1. 程式人生 > >ACM-ICPC 2018 南京賽區網路預賽(ABCDEFGHIJKL所有題題解大全)

ACM-ICPC 2018 南京賽區網路預賽(ABCDEFGHIJKL所有題題解大全)

新高一蒟蒻隊的第一次ACM……
賽場上和隊友時間安排不太恰當……只過了9題,第10題差半個小時2333……/還是自己太弱了
賽後經過一段時間把所有題全部A掉了2333……
題目完成表:
wzy(juruo):ABI(AC),CJ(思路+最初程式碼)
cy(dalao):EGL,C(Debug)
qzr(dalao):D(9題中最難的題),J(Debug)
時間沒有完全抓緊,不然再把K做出來是非常有可能的……(畢竟cy大佬早已想到了思路)
因此下面放的程式碼就是上面對應的人的程式碼(應該不會有版權問題吧2333),剩下來三道題是wzy juruo寫的。
A:題目連結
這道題在聊天中開場7分鐘A掉了2333……真的是簽到題……直接輸出n-1即可。證明略。
B:

題目連結
這道題似乎也是比較水的吧……單調棧+dp裸題。
我們先找到每個點向上走最近的黑點到它的距離記為 f ( i , j ) ,再考慮列舉合法矩形的右下角,並且找到滿足
k < j
f ( i , j )
>= f ( i , k )
的最大的k,這樣對於當前合法的左端點<=k的矩形,必然和右下角再k的合法矩形一一對應,如果左端點>=k,答案就是 f ( i , j ) ( j k ) 。於是假設當前點答案為 g ( i , j ) ,則 g ( i , j ) = g ( i , k ) + f ( i , j ) ( j k )
f和g都可以直接dp出來,最大的k也可以單調棧找一下,然後就做完了(剛開始看AC人數少沒敢做2333……)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 100005, maxm = 105;
int f[maxm], sta[maxm], T, n, m, K;
bool mt[maxn][maxm];
ll dp[maxm];
int main(){
    scanf("%d", &T);
    for(int cs = 1; cs <= T; cs++){
        memset(mt, 0, sizeof(mt));
        memset(f, 0, sizeof(f));
        memset(dp, 0, sizeof(dp));
        scanf("%d%d%d", &n, &m, &K);
        for(int i = 1; i <= K; i++){
            int x, y; scanf("%d%d", &x, &y);
            mt[x][y] = 1;
        }
        ll res = 0;
        for(int i = 1; i <= n; i++){
            int tp = 0;
            for(int j = 1; j <= m; j++){
                if(mt[i][j]) f[j] = 0;
                else ++f[j];
                while(tp > 0 && f[sta[tp]] >= f[j]) --tp;
                if(f[j]){
                    dp[j] = dp[sta[tp]] + (ll)f[j] * (j - sta[tp]);
                    res += dp[j];
                } else dp[j] = 0;
                sta[++tp] = j;
            }
        }
        printf("Case #%d: %lld\n", cs, res);
    }
    return 0;
}

C:題目連結
這道題大模擬不解釋……(我居然用multiset寫的,看來對stl依賴太多不是什麼好事2333)

#include <bits/stdc++.h>
using namespace std;

const int maxn = 205;
multiset<int> ss[maxn];
int sta[20005], T, n, m;
const int rnk[15] = {0, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
const int id[15] = {0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2};
int main(){
    scanf("%d", &T);
    for(int cs = 1; cs <= T; cs++){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++)
            scanf("%d", sta + (m - i + 1));
        int tp = m;
        for(int i = 1; i <= n; i++){
            ss[i].clear();
            int k = min(5, tp);
            for(int j = 0; j < k; j++)
                ss[i].insert(rnk[sta[tp--]]);
        }
        int p = 1, t = 0, num = -1;
        while(true){
            if(t == n - 1){
                for(int i = 0; i < n; i++){
                    if(tp == 0) break;
                    int k = (i + p - 1) % n + 1;
                    ss[k].insert(rnk[sta[tp--]]);
                }
                num = -1;
            }
            int flag = 0;
            if(num == -1){
                num = *ss[p].begin();
                ss[p].erase(ss[p].begin());
            } else if(ss[p].find(num + 1) != ss[p].end()){
                ss[p].erase(ss[p].find(++num));
            } else if(ss[p].find(13) != ss[p].end())
                ss[p].erase(ss[p].find(num = 13));
            else flag = 1;
            if(flag) ++t;
            else t = 0;
            if(ss[p].empty()) break;
            if(num == 13){
                for(int i = 0; i < n; i++){
                    if(tp == 0) break;
                    int k = (i + p - 1) % n + 1;
                    ss[k].insert(rnk[sta[tp--]]);
                }
                num = -1;
            } else p = p % n + 1;
        }
        printf("Case #%d:\n", cs);
        for(int i = 1; i <= n; i++){
            if(ss[i].empty()) puts("Winner");
            else{
                int sum = 0;
                for(multiset<int>::iterator it = ss[i].begin(); it != ss[i].end(); it++)
                    sum += id[*it];
                printf("%d\n", sum);
            }
        }
    }
    return 0;
}

D:題目連結
平面幾何神題,qzr julao上來就想出來的題Orzzzzzz……
首先整個圓在凸包裡可以轉化為把凸包所有邊向內移動半徑的長度,然後圓心在新凸包裡即可。新凸包用半平面交算一下就行。考慮最大價值是多少,拆一下項就會發現這個東西實際上是三個圓心構成三角形面積的兩倍,然後旋轉卡殼求凸包內最大三角形即可。複雜度 O ( T n 2 ) 。(qzr巨佬說他的程式碼暫時不給看2333)
E:題目連結
這道題就是給你一些依賴關係,然後最大化某個值,裸的狀壓dp,初始值注意一下就行。(具體的思路其它部落格很多)
不過cy julao似乎非常喜歡記憶化搜尋??

#include<bits/stdc++.h>
#define pii pair< int,int> 
#define ll long long
#define pll pair<long long,long long>
using namespace std;
int buf[80];
template<class T> inline bool getd(T& x){
    int ch=getchar();
    bool neg=false;
    while(ch!=EOF && ch!='-' && !isdigit(ch)) ch=getchar();
    if(ch==EOF) return false;
    if(ch=='-'){
        neg=true;
        ch=getchar();
    }
    x=ch-'0';
    while(isdigit(ch=getchar())) x=x*10+ch-'0';
    if(neg) x=-x;
    return true;
}

template<class M> inline void putd(M x)
{
    int p=0;
    if(x<0){
        putchar('-');
        x=-x;
    }
    do{
        buf[p++]=x%(ll)10;
        x/=(ll)10;
    }while(x);
    for(int i=p-1;i>=0;i--) putchar(buf[i]+'0');
    putchar('\n');
}
int n;
ll inf=0x3f3f3f3f3f3f3f3f;
ll dp[1<<20];pii k[25];int need[25];int kl[25];int vis[1<<20];
int has[1<<20];ll ans=0;
int dfs(int mask)
{
    if(vis[mask]) return dp[mask];
    vis[mask]=1;
    for(int i=0;i<n;i++)
    {
        if(kl[i]&mask) continue;
        ll cc=k[i].first*has[mask]+k[i].second;
        if((need[i]&mask)==need[i]) dp[mask]=max(dp[mask],dfs(mask|kl[i])+cc);
    }
    //cout<<mask<<":"<<dp[mask]<<"  "; 
    ans=max(ans,dp[mask]);
    dp[mask]=max(dp[mask],0ll);
    return dp[mask];
}
int main()
{
    getd(n);for(int i=0;i<22;i++) kl[i]=(1<<i);
    for(int i=0;i<(1<<20);i++) has[i]=__builtin_popcount(i)+1; 
    for(int i=0;i<n;i++)
    {
        getd(k[i].first);getd(k[i].second);
        int tot;getd(tot);
        while(tot--)
        {
            int id;getd(id);id--;need[i]|=kl[id];
        }
    }
    dfs(0);
    putd(ans);
}

F:題目連結
這道題在思維上有點難,而且實現上也比較毒瘤……
我們考慮如果在一棵樹上從根節點出發回到根節點的期望邊數是多少。不妨設 f ( x ) 表示x節點回到root的期望邊數,顯然我們有f(root)=0.接下來考慮暴力的高斯消元(d(x)表示x的度數):

f ( u ) = ( ( u , v ) E f ( v ) / d ( u ) ) + 1
直接高消不現實,我們考慮它有什麼特殊性質。考慮葉節點u,則 f ( u ) = f ( p a r ) + 1 。對於倒數第二層節點u,則 f ( u ) = f ( p a r ) + 2 d ( u ) 1 ,於是我們大膽假設,對於任意 f ( u ) ,存在 g ( u ) 使 f ( u ) = f ( p a r ) + g ( u ) 。那麼 g ( u ) 到底是什麼呢?最後兩層的規律告訴我們, g ( u ) = 2 s ( u ) 1 ,其中s(u)表示以u為根的子樹大小。通過數學歸納法可以證明這個結論。
於是我們考慮答案是什麼,由於第一步會任意走到根節點的一個子節點當中且不可能跳轉到其它兄弟節點上,於是
a n s = ( ( r o o t , v ) E f ( v )