ICPC2019上海區域賽 部分題解(正在更新)


K. Color Graph

題意:

給定一個簡單圖,點個數<=16,刪去部分邊后,使得該圖中無邊數為奇數得環,問剩下的邊數最大為多少?

思路:

如果一個圖中無奇數邊的環,那么這個圖一定是個二分圖。只要枚舉二分圖的左部,統計所有從左部到右部的邊個數,答案就是枚舉出的所有邊數的最大值。(因為最優解一定也是一個二分圖,所以一定會被枚舉到)

//賽后補題,只過樣例,僅供參考
#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
const int maxm=1e4+5;
struct edge{
    int u,v;
}E[maxm];
int tot=0;
void addedge(int u,int v){
    E[++tot].u=u;
    E[tot].v=v;
}
int color[maxn];
int main(){
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++){
        int n,m;
        scanf("%d%d",&n,&m);
        fill(color,color+1+n,0);
        tot=0;
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        int ans=0;
        for(int meijv=0;meijv<=(1<<n)-1;meijv++){
            int mj=meijv;
            for(int i=1;i<=n;i++){
                if(mj&1){
                    color[i]=1;
                }
                else color[i]=0;
                mj>>=1;
            }
            int res=0;
            for(int i=1;i<=tot;i++){
                if(color[E[i].u]!=color[E[i].v]){
                    res++;
                }
            }
            ans=max(ans,res);
        }
        printf("Case #%d: %d\n",kase,ans);
    }
}

D. Spanning Tree Removal

題意:

給定一個n階的完全圖,每次操作是從圖中移除一棵生成樹的所有邊,問最多能進行多少次這樣的操作?輸出操作次數和每次移除的生成樹的邊。

思路:

n階完全圖共有n*(n-1)/2條邊,一棵生成樹有n-1條邊,很容易猜到能進行n/2次操作,接下來就是如何構造的問題。

下面給出一種直接構造的方法(奇數就孤立出一個點隨便連即可)

avatar

//賽后補題,只過樣例,僅供參考
#include <bits/stdc++.h>
using namespace std;
int main(){
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++){
        int n;
        scanf("%d",&n);
        printf("Case #%d: %d\n",kase,n/2);
        if(n%2==0){
            for(int i=1;i<=n/2;i++){
                printf("%d %d\n",i,i+1);
                for(int j=1;j<=n/2-1;j++){
                    int u=i+j;
                    int v=(u+n-j*2-1)%n+1;
                    printf("%d %d\n",u,v);
                    printf("%d %d\n",v,u+1);
                }
            }
        }
        else{
            n--;
            for(int i=1;i<=n/2;i++){
                printf("%d %d\n",i,i+1);
                for(int j=1;j<=n/2-1;j++){
                    int u=i+j;
                    int v=(u+n-j*2-1)%n+1;
                    printf("%d %d\n",u,v);
                    printf("%d %d\n",v,u+1);
                }
                printf("%d %d\n",i,n+1);
            }
        }
    }
}

H. Tree Partition

題意:

給出一棵點權樹,一個樹的大小定義為所有點的權值和。問將一棵樹分為k棵子樹,如何分割才能使所有樹的大小的最大值最小?

思路:

二分答案,已知最大連通子圖的大小x后,只要在樹上從樹根向上dp子樹的大小即可。如果一個子樹u的大小大於x,則先選擇u最大的兒子v切除(即切割邊u,v),這樣能保證剩下的部分大小盡可能地小。這樣保證了圖上所有的連通子圖的都是小於x的,同時也是用貪心的方法選擇切割方案(每個子樹都盡可能地取到最大,使剩下部分盡可能小),得到的就是最小的切割次數。

實現方法:若判斷發現一個節點u的權值大於x,則將他的兒子節點排序,從大到小依次刪除,直到u的權值小於x。

//賽后還原,僅供參考
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
struct edge{
    int v,next;
}E[maxn];
int head[maxn],tot;
void addedge(int u,int v){
    E[++tot].v=v;
    E[tot].next=head[u];
    head[u]=tot;
}
ll a[maxn],sum[maxn];
int flag=0,cnt;
int n,k;
void dfs(int u,int fa,ll x){
    sum[u]=a[u];
    if(sum[u]>x||flag==0){
        flag=0;
        return;
    }
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v;
        if(v!=fa){
            dfs(v,u,x);
            sum[u]+=sum[v];
        }
    }
    if(sum[u]>x){
        vector<ll>V;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            if(v!=fa){
                V.push_back(sum[v]);
            }
        }
        sort(V.begin(),V.end());
        while(sum[u]>x){
            cnt++;
            sum[u]-=V.back();
            V.pop_back();
        }
    }
    if(cnt>k-1){
        flag=0;
        return;
    }
}
bool check(ll x){
    flag=1;cnt=0;
    dfs(1,0,x);
//    printf("%lld:%d\n",x,flag);
    if(flag)
        return 1;
    else
        return 0;
}
int main(){
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++){
        scanf("%d%d",&n,&k);
        fill(head,head+1+n,0);
        fill(sum,sum+1+n,0);
        tot=0;
        for(int i=1;i<=n-1;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        ll l=0,r=1e14+5;//左開右閉
        while(r-l>1){
            ll mid=(r+l+1)/2;
            if(check(mid))
                r=mid;
            else
                l=mid;
        }
        printf("Case #%d: %lld\n",kase,r);
    }
}

E. Cave Escape

題意:

給定一個\(n * m\)的格子矩陣,其中有一個格子是起點,一個格子是終點。從起點開始移動,每次能移動到有相鄰邊的格子中,每個格子都有一個權值v,若從點a移動到點b,且b點未被訪問過,則可以獲得\(Va*Vb\)的收益,若移動到終點,可以選擇先不出去,繼續在圖上亂走,問如何可以使得走出終點后獲得得收益最大?(只需要輸出最大收益即可)

思路:

很顯然終點在哪是對答案完全沒有影響的,只要在矩陣中亂走獲得最大收益再出去即可。

我們可以將這個矩陣轉化為一個無向圖,圖中的點就是矩陣的格點,相鄰格點之間有一條邊,長度為它們權值的乘積。只要在這個圖上跑一遍最大生成樹,樹的大小就是最大收益。為什么起點也是對答案沒有影響?因為要達到最大收益,最好的方法就是將圖中每一個都遍歷一遍,因為多遍歷一個點是不會虧的,可以通過已經遍歷到的任意點往新的點走來得到收益(已經遍歷過的格子在矩陣中是連通的,可以到處轉移),這不就是生成樹嗎?

//賽后補題,只過樣例,僅供參考
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
const int maxm=4e6+5;
struct edge{
    int u,v;
    ll w;
}E[maxm];
bool cmp(edge a,edge b){
    return a.w>b.w;
}
int tot=0;
void addedge(int u,int v,ll w){
    E[++tot].u=u;
    E[tot].v=v;
    E[tot].w=w;
}
int fa[maxn*maxn];
int n,m;
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
ll kruskal(){
    for(int i=1;i<=n*m;i++){
        fa[i]=i;
    }
    sort(E+1,E+1+tot,cmp);
    int cnt=0;
    ll ans=0;
    for(int i=1;i<=tot;i++){
        int u=E[i].u;
        int v=E[i].v;
        int fu=find(u);
        int fv=find(v);
        if(fu!=fv){
            fa[fu]=fv;
            ans+=E[i].w;
            cnt++;
        }
        if(cnt==n*m-1)return ans;
    }
}
ll x[maxn*maxn];
ll V[maxn][maxn];
int xx[]={1,0,0,-1};
int yy[]={0,1,-1,0};
int main(){
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++){
        int sr,sc,tr,tc;
        scanf("%d%d%d%d%d%d",&n,&m,&sr,&sc,&tr,&tc);
        tot=0;
        ll A,B,C,P;
        scanf("%lld%lld%lld%lld%lld%lld",&x[1],&x[2],&A,&B,&C,&P);
        for(int i=3;i<=n*m;i++){
            x[i]=(x[i-1]*A+x[i-2]*B+C)%P;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                V[i][j]=x[(i-1)*m+j];
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                for(int pp=0;pp<4;pp++){
                    int it=i+xx[pp];
                    int jt=j+yy[pp];
                    int u=(i-1)*m+j;
                    int v=(it-1)*m+jt;
                    if(it>=1&&it<=n&&jt>=1&&jt<=m){
                        addedge(u,v,V[i][j]*V[it][jt]);
                    }
                }
            }
        }
        ll ans=kruskal();
        printf("Case #%d: %lld\n",kase,ans);
    }
}

B. Prefix Code

題意:

給出一系列數字,長度均小於10,問是否有一個數是其他數的前綴?

思路:

Trie樹模板題。記錄單詞的終末,前綴包含的單詞個數即可。若一個點是單詞終末且前綴包含單詞個數>1,則輸出No。

//輸入字串用s+1,函數調用用s
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int T[maxn][12];
int num[maxn];
int isend[maxn];
int tot=1;
void add(char *s){
    int l=strlen(s+1);
    int rt=1;
    for(int i=1;i<=l;i++){
        if(T[rt][s[i]-'0']==0){
            T[rt][s[i]-'0']=++tot;
            rt=tot;
        }
        else{
            rt=T[rt][s[i]-'0'];
        }
        num[rt]++;
    }
    isend[rt]=1;
}
void init(){
    for(int i=0;i<=tot;i++){
        memset(T[i],0,sizeof(T[i]));
        num[i]=isend[i]=0;
    }
    tot=1;
}
char s[15];
int main(){
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++){
        init();
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            add(s);
        }
        int flag=1;
        for(int i=1;i<=tot;i++){
            if(isend[i]&&num[i]>1){
                flag=0;
                break;
            }
        }
        if(flag)
            printf("Case #%d: Yes\n",kase);
        else
            printf("Case #%d: No\n",kase);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM