“科大訊飛杯”第18屆上海大學程序設計聯賽春季賽暨高校網絡友誼賽 A-L 解題報告

A 組隊比賽

題目類型:思維

題目鏈接:

A-組隊比賽

題目大意:

給你四個數組,兩兩一組,每組實力值爲隊內實力值之和,求解兩組差值最小

解題思路:

四個數組排序,最大與最小,第二與倒二,絕對值下差值即可

代碼:

int a[4];
int main(){
    cin >> a[0] >> a[1] >> a[2] >> a[3];
    sort(a, a+4);
    cout << abs(a[0] + a[3] - a[1] - a[2]) << '\n';
}

B 每日一報

題目類型:模擬、排序

題目鏈接:

B-每日一報

題目大意:

給你一份n個學生的數據,裏面包含學生的測溫時間、學號、體溫三個信息,現在要求你取出大於38.0的學生信息,並按照如下要求進行排序

鏈接:https://ac.nowcoder.com/acm/contest/5278/B
來源:牛客網
報送日期不一致的,則日期較近的在上,日期較久遠的在下;
報送日期一致體溫不一致的,則體溫高的在上,體溫低的在下;
報送日期和體溫都一致的,則學號小的在上,學號大的在下。

解題思路:

通過結構體排序實現

代碼:

struct sit{
    string day, num; DB tem;
} a[101];
bool cmp (sit fir, sit sed){
    if (fir.day != sed.day) return fir.day > sed.day;
    else if (fir.tem != sed.tem) return fir.tem > sed.tem;
    else {
        return fir.num < sed.num;
    }
}
int main(){
    int n, k = 0; cin >> n;
    REP(i, n){
        string rday, rnum; DB rtem;
        cin >> rday >> rnum >> rtem;
        if (rtem >= 38.0) a[k].day = rday, a[k].num = rnum, a[k].tem = rtem, k++;
    }
    OT(k);
    sort(a, a+k, cmp);
    REP(i, k) {
        cout << a[i].day << " " << a[i].num << " "; printf("%.1f\n", a[i].tem);
    }
}

C 最長非公共子序列

題目類型:思維

題目鏈接:

C-最長非公共子序列

題目大意:

給你兩段字符串,現在要求你求解,最長不相同子序列的長度

解題思路:

其實很簡單,原比最長公共子序列容易的多,兩個字符串完全相同的情況下是不可能有不相同子序列的,而一但兩個字符串不同直接輸出較長那個字符串的就可以了,簡單的思維題

代碼:

int main(){
string s1, s2; cin >> s1 >> s2;
if (s1 == s2) cout << "-1" << '\n';
else cout << max(SZ(s1), SZ(s2)) << '\n';
}

D 組隊比賽

題目類型:思維

題目鏈接:

D-最大字符集

題目大意:

給你一個n,要求你求解出最多01字符串,這些字符串要求相互不是子串,且任意兩兩長度不同

解題思路:

實際上當你寫幾個01字符串你會發現幾個要點

  1. 1或0會是所有長度大於等於的字符串的子串(這一點是告訴你要特判n=2的情況)
  2. 兩個1中間不斷加0比不可能是上一串的子串

針對第二個我寫幾個,就很好理解了,

11
101
1001
10001
100001
你會發現恰好滿足長度不一致,且不互爲子串的條件(00也一樣),所以只需要特判n=1和n=2的情況就可以了

代碼:

int main(){
    int n; cin >> n; 
    if (n == 1){ cout << 1 << '\n' << 1 << '\n';}
    else if (n == 2) cout << "2\n" << 0 << '\n' << "11" << '\n'; 
    else { cout << n - 1 << '\n';
        REP(i, n - 1){
            cout << "1"; REP(j, i){cout << "0";} cout << 1 << '\n';
        }
    }
}

E 組隊比賽

題目類型:思維

題目大意:

給你一個長度爲n的數組,每個位置的值表示美味度(即a[i]a[i]),你可以從頭或者從尾食用,一秒只能食用一個,並且未被食用的部分會美味度-1,要求你求解最大的總美味度

解題思路:

這題你可能會思考,到底是從頭吃呢還是從尾吃呢,其實是沒差別的,因爲

他用數字標識了每個部分的美味度(可能是負的)

美味度可能是負值,你要減去的數量就一定會

(n1)n/2(n - 1) * n / 2

可以邊加邊減,也可以全部加完再減

代碼:

有人可能會是第一種方法,卻wa了,n必須要是是LL,爲什麼n要是LL呢?
明明題目給的數據沒有溢出啊,是因爲在計算(n1)n/2(n - 1) * n / 2的時候溢出的,這個可能導致你的wa(還好我用的是第二個方法QAQ)

int main(){
    LL n; cin >> n;
    LL ans = 0;
    REP(i, n){
        LL x; scanf("%lld",&x);
        ans += x;
    }
    ans -= (n - 1) * n / 2;
        OT(ans);
}
int main(){
    int n; cin >> n;
    LL ans = 0;
    REP(i, n){
        int x; scanf("%d",&x);
        ans += x - i;
    }
        OT(ans);
}

F 組隊比賽

題目鏈接:

F-組隊比賽

題目類型:模擬

題目大意:

母親節和父親節你需要送禮,對於每一個日期,輸出下一個需要送禮的日子(下一個需要送禮的日子可能是母親節也可能是父親節)

解題思路:

依照模擬,需要判斷的實際上就是下一個節日是什麼,
母親節在8到14之間變動

if(a[i] < 8)   a[i] += 7;

,父親節在15到21之間變動,

if(b[i] < 15)  b[i] += 7;

代碼:

const int maxn = 200;
int a[maxn], b[maxn];
int main(){
    int n = 0, p;
    a[0] = 14;
    b[0] = 18;
    FOR_1(i, 1, 103)
    {
        a[i] = a[i-1]-1;
        b[i] = b[i-1]-1;
        if(i % 4 == 0 && i != 100)
        {   
            a[i]--; 
            b[i]--; 
        }//閏年
        if(a[i] < 8)   a[i] += 7;
        if(b[i] < 15)  b[i] += 7;
    }
    int _; for(scanf("%d", &_); _; _--){
        int y, m, d;scanf("%d%d%d",&y,&m,&d);   n = y - 2000; //易知是從2000年開始
        if(m > 6 || m == 6 && d >= b[n])
            printf("Mother's Day: May %dth, %d\n",a[n+1], y+1);
        else if(m < 5 || m == 5 && d < a[n])
            printf("Mother's Day: May %dth, %d\n",a[n], y);
        else if(b[n]!=21)
            printf("Father's Day: June %dth, %d\n",b[n], y);
        else    printf("Father's Day: June 21st, %d\n", y);
    }
}

G 血壓遊戲

題目鏈接

G-血壓遊戲

題目類型:樹

題目大意:

官方題意

Compute 有一棵 n 個點,編號分別爲 1∼n 的樹,其中 s 號點爲根。
Compute 在樹上養了很多松鼠,在第 i 個點上住了 ai 個松鼠。
因爲某些緣故,它們開始同時向根節點移動,但它們相當不安分,如果在同一個節點上,它們就會打起來,簡單地來說以下事件會依序發生:
如果一個節點上有 2 只或 2 只以上的松鼠,他們會打架,然後這個節點上松鼠的數量會減少 1;
根節點的所有松鼠移動到地面,位於地面上的松鼠不會再打架;
所有松鼠同時朝它們的父節點移動。
所有事件各自都在一瞬間完成,直至樹上沒有松鼠。
現在 Compute 想知道最終有多少隻松鼠到達了地面。

亂搞題意

給你一個樹,每個節點上都有a[i]a[i]個松鼠,所有位置上的松鼠都是向父節點進行移動的,而根節點上的松鼠是向地面上移動,就是從根節點移動出去
每次的操作時這樣的

  1. 如果一個節點上有 2 只或 2 只以上的松鼠,他們會打架,然後這個節點上松鼠的數量會減少 1;
  2. 根節點的所有松鼠移動到地面,位於地面上的松鼠不會再打架;
  3. 所有松鼠同時朝它們的父節點移動。

題目問的是最後到地面上的松鼠有多少隻

解題思路:

結論1

由於每一步的松鼠都是向父節點移動的,只有一個節點上有2只或者兩隻以上的松鼠纔會打架,那麼可以確定 只有同一層上的松鼠纔會相互打架

  1. 因爲是同一時間進行向父節點進行移動的

思考1

由於是子節點向父節點進行移動所以可以考慮子樹合併的思想,(可以思考是不是樹上啓發式合併的方法)

思考2

由於是不斷的向上移動,也就是每個節點需要不斷的查詢,那麼可能會面臨較多次數的查詢次數, 可以思考是不是需要使用虛樹這個數據結構呢?

虛樹中包含了所有的關鍵點,也包含了所有關鍵點兩兩之間的 LCA(lowest common ancestor, 最近公共祖先)(LCA 的數量不超過關鍵點的個數,稍後有證明),這就保證了虛樹不會喪失原有的樹形結構,同時儘可能地壓縮了樹的大小。同時虛樹中 uu 到 vv 的邊權定義爲原樹中 uu 到 vv 的最短路徑
關於虛樹虛樹學習筆記

dp[x]=∑t∈sonx ,dp[t]>0max{1,dp[t]−(deep[t]−deep[x])}

 for(auto k : ma[rt])
        if(k.second)
            ans += max(1ll, k.second-tag[rt]);

總結

方法1 .利用DFS序合併區間虛樹的線段樹 && 啓發式合併的思想
方法2. 虛樹構建一下,再進行dp,根據子節點情況去計算父節點情況(將子節點合併到父節點上),滿足通過計算有限集統計總和情況

樹上合併式啓發

void merge(int x, int y) {
  int xx = find(x), yy = find(y);
  if (size[xx] < size[yy]) swap(xx, yy);
  fa[yy] = xx;
  size[xx] += size[yy];
}

虛樹
我們將兩個大小不一樣的集合,我們將小的集合合併到大的集合中,而不是將大的集合合併到小的集合中。

爲什麼呢?這個集合的大小可以認爲是集合的高度(在正常情況下),而我們將集合高度小的併到高度大的顯然有助於我們找到父親
讓高度小的樹成爲高度較大的樹的子樹,這個優化可以稱爲啓發式合併算法。

代碼:

const int maxn = 200100;
 
int n, rt;
LL ans;
int a[maxn];
vector<int> G[maxn];
 
int fa[maxn], dep[maxn];
LL tag[maxn];
 
map<int, long long> ma[maxn];
 
void inse(int u, int d, long long val)
{
    if(!ma[u].count(d))
        ma[u][d] = val+tag[u];
    else
        ma[u][d] = max(ma[u][d], tag[u]+1) + val;
}
 
void merg(int u, int v)
{
    if(ma[v].size() > ma[u].size())
    {
        swap(ma[u],ma[v]);
        swap(tag[u],tag[v]);
    }
    for(auto i : ma[v])
        if(i.second)
            inse(u, i.first, max(i.second-tag[v], 1ll));
}//將小的子樹合併到大的父節點中
void dfs(int u)
{
 
    dep[u] = dep[fa[u]] + 1;
    for(int v : G[u])
    {
        if(v == fa[u]) continue;
        fa[v] = u;
        dfs(v);
        merg(u, v);
    }
    if(a[u])
        inse(u, dep[u], a[u]);
    tag[u]++;
} 
 
int main()
{
    scanf("%d%d", &n, &rt);//點的數量和根的編號
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for(int i = 2; i <= n; ++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(rt);
    for(auto k : ma[rt])
        if(k.second)
            ans += max(1ll, k.second-tag[rt]);
    printf("%lld\n", ans);
}

H 紙牌遊戲

題目鏈接:

H-紙牌遊戲

題目類型:數學

題目大意:

給你一個字符串s 要求你拼出一個長度爲 k 的同時能被 3 整除的最大非負整數,並且不能有前導0

解題思路:

什麼樣的數能被3整除?

該數字的各個位置上的數相加之和能被3整除,那麼該數也一定能被3整除

注意

一開始我求簡便,是這麼做,因爲要最大,所以我直接排序,取較大的k個數字,然後判斷是否能被3整除,如果不行我選一位去枚舉和後面一位交換直到成立,這麼做是不對的,因爲當需要出現交換兩個的時候就會出錯,這裏給出一下樣例,有興趣的可以跑一下

輸入
998244151 3
輸出
984

正文

1.我們因爲每位字符純粹是我們自己決定的,沒有什麼其他的要求,所以我們完全可以統計0-9數字出現的次數去進行組合
2.取餘3爲1的數字和取餘3位2的數字組合之和一定能被3整除,例如2和1
3.我們在組合區間枚舉出符合條件最大的就可以

代碼:

int cntmod[3]; // 被3取餘後只會有3種情況 0, 1, 2,這裏對三種情況進行一下計數
int cnt[10];
bool judge(int x, int m){
    if (!m) return !x;
    int a = cntmod[0], b = cntmod[1],c = cntmod[2];
    int l0 = max(0, m - b - c), r0 = min(a, m);
    //一個除3餘數爲1的數和一個除3餘2的數匹配就是一個能被3整除的數,例如1和2, 那麼我們就枚舉這些組合判斷是否能被整除
    for(int i = l0; i <= r0; i++){
        int t = ((2 * (m - i) - x) % 3 + 3 ) % 3;
        int l1 = max(0, m - i - c), r1 = min(b, m - i);
        while(l1 % 3 != t) l1++;
        if(l1 <= r1) return true;
    }
    return false;
}

void init(){
    //初始化
    memset(cntmod, 0, sizeof cntmod);
    memset(cnt, 0, sizeof cnt);
}
int main(){
    int _;
    for(scanf("%d", &_); _; _--){
        init();
        string s; int k;
        cin >> s >> k;
        int len = SZ(s);
        FOR(i, 0, SZ(s)){
            cnt[s[i] - '0']++; cntmod[(s[i] - '0') % 3]++;
        }
        //統計
        int n;
        for(int i = 9, j = n = 0; i >= 0; i--){
            //枚舉各個數出現次數之前已經統計過了,現在只是運用
            while(cnt[i] > 0 && n < k){
                int t = (i + j) % 3;
                cnt[i]--, cntmod[i % 3] --;//如果這數字字i使用了,就減去
                if (!judge((3 - t) % 3, k - n - 1)){
                    cnt[i]++, cntmod[i % 3]++;
                    break;//如果不能被整除那麼就回溯,不使用這個i
                }
                s[++n] = '0' + i;j = t;
            }
        }
        //s[n + 1] = '\0';
        if(n < k || (s[1] == '0' && n > 1)) OT("-1");//出現前導0的情況
        else {
            FOR_1(i, 1, n){
                cout << s[i];
            }
            OT("");
        }
    }
}

I 組隊比賽

題目類型:

題目大意:

解題思路:

代碼:

J 組隊比賽

題目類型:

題目大意:

解題思路:

代碼:

K 組隊比賽

題目類型:

題目大意:

解題思路:

代碼:

L 組隊比賽

題目類型:

題目大意:

解題思路:

代碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章