zoukankan      html  css  js  c++  java
  • NOIP2018提高组题解

    D1T1:铺设道路

    回忆NOIP2013D2T1 积木大赛,发现这两题唯一的区别就是一个是造山一个是填坑,而把填坑的操作反序就是造山,所以可以直接使用那道题的方法。

    具体方法是,从左到右每次考虑新的一列,若这一列的坑比左边一列浅,那么可以在填左边一列的时候顺便填好这个坑(只要把所有右端点为i-1的操作右端点全部改为i即可),不需要任何操作。若这一列的坑比左边深,那么就必须先将这一列的坑填到与左边平齐,再让左边的操作顺带把这个坑填平。

    于是有:若a[i]<=a[i-1],ans不变,否则ans+=a[i]-a[i-1]。

    期望得分:100

    复杂度:$O(n)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     5 using namespace std;
     6 
     7 const int N=100010;
     8 int n,ans,a[N];
     9 
    10 int main(){
    11     freopen("road.in","r",stdin);
    12     freopen("road.out","w",stdout);
    13     scanf("%d",&n);
    14     rep(i,1,n){
    15         scanf("%d",&a[i]);
    16         if (a[i]>a[i-1]) ans+=a[i]-a[i-1];
    17     }
    18     printf("%d
    ",ans);
    19     return 0;
    20 }
    road

    D1T2:货币系统

    这题性质十分类似线性基的构造,首先证明两个结论:

    1.最简系统一定是原系统的子集。

    2.最简系统中任何一个面额不能被其余面额表出。

    结论二显然。

    若最简系统出现了一个原系统没有的数x,那么若这个数能被原系统表出,那么它的加入没有任何意义,可以删去,矛盾。

    若不能被表出,那么最简系统就比原系统多表出了一个数x,矛盾。

    结论一得证。

    于是就有了运行策略,先将原系统中所有数从小到大排序(因为能表出某数的面额一定都不比那个数大),逐一判断每个数能否被当前最简系统表出,若不能则加入。

    那么如何判断能否被表出呢,注意到值域不大,那么这个题的原型实际上就是一个完全背包问题,于是每次加入数的时候做一次完全背包即可。

    期望得分:100

    复杂度:$O(n*a_i)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     5 using namespace std;
     6 
     7 const int N=2500010;
     8 int T,n,a[210];
     9 bool f[N];
    10 
    11 int main(){
    12     freopen("money.in","r",stdin);
    13     freopen("money.out","w",stdout);
    14     for (scanf("%d",&T); T--; ){
    15         scanf("%d",&n); int mx=0,ans=0;
    16         rep(i,1,n) scanf("%d",&a[i]),mx=max(mx,a[i]);
    17         rep(i,1,mx) f[i]=0; f[0]=1;
    18         sort(a+1,a+n+1);
    19         rep(i,1,n){
    20             if (f[a[i]]) continue;
    21             ans++;
    22             rep(j,a[i],mx) f[j]|=f[j-a[i]];
    23         }
    24         printf("%d
    ",ans);
    25     }
    26     return 0;
    27 }
    money

    D1T3:赛道修建

    十分套路的一道题。

    算法一:m=1的情况就是要找一条直径。

    期望得分:20

    复杂度:$O(n)$

    算法二:ai=1是一个菊花图,二分答案后尽量匹配出最多的长度>=mid的链。这个贪心或再套一层二分答案均可。

    期望得分:20

    复杂度:$O(nlog n)$

    算法三:bi=ai+1是一条链,二分答案后贪心选取即可。若当前链长度已>=mid则断开,计数器+1。

    期望得分:20

    复杂度:$O(nlog n)$

    以上部分通过数据分治可以得到55分。

    算法四:分支不超过3,只要找到一个度为1的点作为根就是一棵二叉树。在树上做简单DP即可,具体可见满分做法。

    期望得分:55

    复杂度:$O(nlog n)$

    以上部分分通过数据分治可以得到80分。

    算法五:首先想到二分答案转化为可行性问题。问题转化为,能否找到至少m条长度不小于mid的不相交的道路。

    考虑树形DP,这个问题要求最大化两个指标,一是道路条数最大化,二是道路条数最多的情况下每条道路长度最长。

    于是设f[x]表示x的子树中最多能选出多少条长度不小于mid的互不相交的链,g[x]表示x的子树中,在已选出f[x]条合法链后,最多能从根往下延伸出多长的链(这条链不与已选出的f[x]条链相交,它将和x子树之外的点连接成为合法链)。

    我们有结论:一个子树内,必定是先让合法链的个数最多(即最大化f),在此基础上再尽量让从根伸出的链最长(即最大化g)。

    考虑转移,首先f[x]+=f[son],然后合并子树伸出的链。问题转化为找尽量多的不重复数对,是两两和不小于mid,且最后剩下来的最大的数最大。

    先将所有数从小到大排序,然后从小到大考虑(若某个数已经配对则跳过),对于一个数x,在后面找到最小的数y使x+y>=mid,再找y以及y之后的第一个尚未配对的数x与之配对,f++,若不存在则x不与任何数配对,用x更新g。

    可以证明,这个贪心策略一定是最优的,f和g在此策略下都能被最大化。

    至于找y可以直接二分或单调指针扫描,找y后第一个可用的数可以用set或经典的并查集处理。

    期望得分:100

    复杂度:$O(nlog^2 n)$或$O(nlog n)$

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
     6 using namespace std;
     7 
     8 const int N=100010;
     9 int n,m,u,v,w,cnt,L,R,mid,ans,f[N],g[N],a[N],fa[N];
    10 int h[N],to[N<<1],nxt[N<<1],val[N<<1];
    11 void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
    12 int get(int x){ return (fa[x]==x) ? x : fa[x]=get(fa[x]); }
    13 
    14 void DP(int x,int Fa){
    15     f[x]=0; g[x]=0; int tot=0;
    16     For(i,x) if ((k=to[i])!=Fa) DP(k,x),f[x]+=f[k];
    17     For(i,x) if ((k=to[i])!=Fa) a[++tot]=g[k]+val[i];
    18     if (!tot) return;
    19     sort(a+1,a+tot+1);
    20     while (tot && a[tot]>=mid) tot--,f[x]++;
    21     if (!tot) return;
    22     rep(i,1,tot+1) fa[i]=i;
    23     rep(i,1,tot-1){
    24         if (fa[i]!=i) continue;
    25         int r=lower_bound(a+i+1,a+tot+1,mid-a[i])-a;
    26         if (r>tot || a[i]+a[r]<mid) continue;
    27         int t=get(r);
    28         if (t>tot) continue;
    29         f[x]++; fa[i]=get(i+1); fa[t]=get(t+1);
    30     }
    31     rep(i,1,tot) if (fa[i]==i) g[x]=a[i];
    32 }
    33 
    34 int main(){
    35     freopen("track.in","r",stdin);
    36     freopen("track.out","w",stdout);
    37     scanf("%d%d",&n,&m);
    38     rep(i,2,n) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w),R+=w;
    39     while (L<=R){
    40         mid=(L+R)>>1; DP(1,0);
    41         if (f[1]>=m) ans=mid,L=mid+1; else R=mid-1;
    42     }
    43     printf("%d
    ",ans);
    44     return 0;
    45 }
    track

    D2T1:旅行

    首先树的情况不难想到贪心策略,由于走的是一个dfs序,所以每次选最小的相邻的尚未走过的点走过去即可。

    n=m的情况是一个环套树,由于最终的遍历过程中一定有一条边没有被遍历到,那么枚举这条边,删掉后再跑树的情况即可。

    注意如果删的边不在环上会导致图不连通,此时显然得到的遍历序列长度小于n,判掉就好。

    但这样n=m写的太丑的话可能会被卡常,所以最好在走跑dfs的时候实时判断是否比当前字典序是否比最优解大,若是则直接退出。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     5 using namespace std;
     6 
     7 const int N=5010;
     8 int n,m,d,tot,cnt,res[N],tmp[N],ans[N],h[N],to[N<<1],nxt[N<<1],E[N][N];
     9 bool fl,fl1,vis[N];
    10 struct edg{ int u,v; }e[N];
    11 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
    12 
    13 void dfs(int x){
    14     if (fl) return;
    15     res[++tot]=x; vis[x]=1;
    16     if (res[tot]<ans[tot]) fl1=1;
    17     if (res[tot]>ans[tot] && !fl1) { fl=1; return; }
    18     for (int i=h[x]; i; i=nxt[i]){
    19         int k=to[i];
    20         if (!((x==e[d].u && k==e[d].v) || (x==e[d].v && k==e[d].u)) && !vis[k]) dfs(k);
    21     }
    22 }
    23 
    24 int main(){
    25     freopen("travel.in","r",stdin);
    26     freopen("travel.out","w",stdout);
    27     scanf("%d%d",&n,&m);
    28     rep(i,1,n) ans[i]=n+1;
    29     rep(i,1,m){
    30         scanf("%d%d",&e[i].u,&e[i].v);
    31         E[e[i].u][++tmp[e[i].u]]=e[i].v;
    32         E[e[i].v][++tmp[e[i].v]]=e[i].u;
    33     }
    34     rep(i,1,n){
    35         if (!tmp[i]) continue;
    36         sort(E[i]+1,E[i]+tmp[i]+1);
    37         for (int j=tmp[i]; j; j--) add(i,E[i][j]);
    38     }
    39     if (m==n-1){
    40         dfs(1);
    41         rep(i,1,n) printf("%d ",res[i]);
    42         return 0;
    43     }
    44     for (d=1; d<=m; d++){
    45         tot=0; rep(i,1,n) vis[i]=0;
    46         fl=0; fl1=0; dfs(1);
    47         if (tot<n || fl) continue;
    48         rep(i,1,n) ans[i]=res[i];
    49     }
    50     rep(i,1,n) printf("%d ",ans[i]);
    51     return 0;
    52 }
    travel_1

    或者直接找到环,只删环上的边。这在环很小的时候效果极佳。

     1 #include<vector>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
     7 using namespace std;
     8 
     9 const int N=5010;
    10 int n,m,u,v,cnt,tim,tot,top,res[N],ans[N];
    11 int cir[N],vis[N],pre[N],h[N],to[N<<1],nxt[N<<1];
    12 vector<int>a[N];
    13 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
    14 
    15 void dfs(int x){
    16     vis[x]=1; res[++top]=x;
    17     For(i,x) if (!vis[k=to[i]]) dfs(k);
    18 }
    19 
    20 void find(int x){
    21     vis[x]=++tim;
    22     For(i,x) if (!vis[k=to[i]]) pre[k]=x,find(k);
    23         else if (vis[k]>vis[x]){
    24             for (int j=k; j!=x; j=pre[j]) cir[++tot]=j;
    25             cir[++tot]=x;
    26         }
    27 }
    28 
    29 void dfs2(int x,int fa){
    30     vis[x]=1; res[++top]=x;
    31     For(i,x) if ((k=to[i])!=fa && !((x==u && k==v) || (x==v && k==u))) dfs2(k,x);
    32 }
    33 
    34 int main(){
    35     freopen("travel.in","r",stdin);
    36     freopen("travel.out","w",stdout);
    37     scanf("%d%d",&n,&m);
    38     rep(i,1,m) scanf("%d%d",&u,&v),a[u].push_back(v),a[v].push_back(u);
    39     rep(i,1,n){
    40         sort(a[i].begin(),a[i].end());
    41         for (int j=a[i].size()-1; j>=0; j--) add(i,a[i][j]);
    42     }
    43     if (m<n){ dfs(1); rep(i,1,n) printf("%d ",res[i]); return 0; }
    44     find(1);
    45     rep(i,1,n) ans[i]=n+1;
    46     rep(i,1,tot){
    47         top=0; rep(j,1,n) vis[j]=0;
    48         u=cir[i]; v=cir[i%tot+1];
    49         dfs2(1,0); bool flag=0;
    50         rep(j,1,n) if (res[j]!=ans[j]){ flag=res[j]<ans[j]; break; }
    51         if (flag) rep(j,1,n) ans[j]=res[j];
    52     }
    53     rep(i,1,n) printf("%d ",ans[i]);
    54     return 0;
    55 }
    travel_2

    D2T2:填数游戏

    算法一:n,m<=3可以直接手算。

    m=1时:$2^n$

    n=1时:$2^m$

    n=2,m=2:样例

    n=2,m=3:手算得36

    n=3,m=2:手算得36

    n=3,m=3:样例

    期望得分:20

    复杂度:$O(1)$

    开始理性分析下题目的性质:

    题意是,对于任何一个点(x,y),从(x-1,y)出发的任意一条路径均不大于从(x,y-1)出发的任意一条路径。

    那么有两个结论:

    1.不存在$w_{x-1,y}=1$,$w_{x,y-1}=0$的情况。即任何一个对角线都是从左下到右上先1后0(或全0全1)。

    2.若存在x,y使得$w_{x-1,y}=w_{x,y-1}$,那么(x,y)右下方的所有对角线上每个数都相等,及所有离(x,y)的曼哈顿距离相等的数都相等。

    结论一显然。

    若存在某个点(x1,y1)和(x2,y2) (x1,x2>=x0,y1,y2>=y0)使得$w_{x1,y1}=1$,$w_{x2,y2}=0$且这两个点到(x0,y0)的曼哈顿距离相等,那么可以从(x-1,y)出发走到(x1,y1),从(x,y-1)出发走到(x2,y2),那么前者路径的字典序一定会大于后者。

    结论二得证。

    这样证明了两个结论的必要性,理性分析一下可以发现,两个结论合起来就是充要条件。证明大概是讨论多种情况,这里不再叙述。

    于是我们有了:

    算法二:n=2可以忽视结论2,其中(1,1)和(n,m)可以随意选择,其它每对数有(0,0),(1,0),(1,1)三种取法(限于结论一不能取(0,1))。

    很容易得出答案为$4*3^{m-1}$。或者状压DP也行,这里只要压两位所以几乎相当于普通DP。

    期望得分:30

    复杂度:$O(log m)$

    以上部分分通过数据分治可以得到50分。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     5 using namespace std;
     6 
     7 const int N=2000010,mod=1e9+7;
     8 int n,m,f[N][4];
     9 
    10 int main(){
    11     freopen("game.in","r",stdin);
    12     freopen("game.out","w",stdout);
    13     scanf("%d%d",&n,&m);
    14     if (n==1 || m==1){ printf("%d
    ",1<<(n*m)); return 0; }
    15     if (n==3 && m==2){ puts("36"); return 0; }
    16     if (n==3 && m==3){ puts("112"); return 0; }
    17     if (n==5 && m==5){ puts("7136"); return 0; }
    18     f[1][0]=f[1][1]=f[1][2]=f[1][3]=1;
    19     rep(i,2,m){
    20         f[i][0]=f[i][1]=((f[i-1][0]+f[i-1][1])%mod+(f[i-1][2]+f[i-1][3])%mod)%mod;
    21         f[i][2]=f[i][3]=(f[i-1][1]+f[i-1][3])%mod;
    22     }
    23     printf("%d
    ",((f[m][0]+f[m][1])%mod+(f[m][2]+f[m][3])%mod)%mod);
    24     return 0;
    25 }
    game(50分)

    算法三:n=3时,结论二只会限制2,3行的一些数对相等,考虑状压DP解决。

    f[i][S][0/1]表示,考虑到第i列,第i列的状态为S (0<=S<8),是/否存在结论二限制 的方案数。转移考虑这一列和上一列是否会构成限制。

    期望得分:15

    复杂度:$O(64m)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     4 using namespace std;
     5 
     6 const int N=1000010,mod=1e9+7;
     7 int n,m,ans,f[N][10][2];
     8 
     9 int main(){
    10     freopen("game.in","r",stdin);
    11     freopen("game.out","w",stdout);
    12     scanf("%d%d",&n,&m); int ed=(1<<n)-1;
    13     rep(S,0,ed) f[1][S][0]=1;
    14     rep(i,1,m-1){
    15         rep(S,0,ed) if (f[i][S][0] || f[i][S][1]){
    16             rep(S1,0,ed){
    17                 if ((S1&4 && !(S&2)) || ((S1&2) && !(S&1))) continue;
    18                 if ((S1&4 && S&2) || (!(S1&4) && !(S&2))) f[i+1][S1][1]=(f[i+1][S1][1]+f[i][S][0])%mod;
    19                     else f[i+1][S1][0]=(f[i+1][S1][0]+f[i][S][0])%mod;
    20                 if (!(S1&2) && (S&1)) continue;
    21                 f[i+1][S1][1]=(f[i+1][S1][1]+f[i][S][1])%mod;
    22             }
    23         }
    24     }
    25     rep(S,0,ed) ans=(ans+(f[m][S][0]+f[m][S][1])%mod)%mod;
    26     printf("%d
    ",ans);
    27     return 0;
    28 }
    game(DP)

    算法四:根据结论一和结论二给暴搜剪枝,可以在15s内轻松打完8*8的表。

    期望得分:15

    复杂度:打表后$O(1)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     4 using namespace std;
     5 
     6 const int N=1010,mod=1e9+7;
     7 int n,m,ans,tot,a[N][N],d[N];
     8 struct P{ int x,y; }p[N];
     9 void dfs(int x,int y);
    10 
    11 void Do(int x,int y){
    12     if (x>1 && y<m && a[x][y]==a[x-1][y+1]) p[++tot]=(P){x,y+1};
    13     dfs(x,y+1);
    14     if (x>1 && y<m && a[x][y]==a[x-1][y+1]) tot--;
    15 }
    16 
    17 void dfs(int x,int y){
    18     if (x>n){ ans++; return; }
    19     if (y>m) { dfs(x+1,1); return; }
    20     rep(i,1,tot) if (p[i].x<x && p[i].y<=y && y<m){
    21         if (~a[x][y] && a[x][y]!=a[x-1][y+1]) { a[x][y]=-1; return; }
    22         a[x][y]=a[x-1][y+1];
    23     }
    24     if (~a[x][y]){ Do(x,y); a[x][y]=-1; return; }
    25     a[x][y]=1; Do(x,y); a[x][y]=0;
    26     if (!(x>1 && y<m && a[x][y]<a[x-1][y+1])) Do(x,y);
    27     a[x][y]=-1;
    28 }
    29 
    30 int main(){
    31     freopen("game.in","r",stdin);
    32     freopen("game.out","w",stdout);
    33     for (n=1; n<=8; n++){
    34         for (m=1; m<=8; m++){
    35             rep(i,1,n) rep(j,1,m) a[i][j]=-1;
    36             ans=0; dfs(1,1); printf("%d ",ans);
    37         }
    38         puts("");
    39     }
    40     return 0;
    41 }
    game(打表)

    以上部分分通过数据分治可以得到80分。

    算法五:稍微把表打大一点,暴力找规律即可。不会证明,没什么好说的。

    数据范围完全不知道出于什么意义。

    有公式ans[n][n]=ans[n-1][n-1]*8-5*2^n,所以事实上出得多大都是没有问题的。

    期望得分:100

    复杂度:$O(log n+log m)$

     1 #include<cstdio>
     2 #include<algorithm>
     3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     4 using namespace std;
     5 
     6 const int mod=1e9+7;
     7 const int d[10]={0,2,12,112,912,7136,56768,453504,3626752};
     8 int n,m;
     9 
    10 int ksm(int a,int b){
    11     int res=1;
    12     for (; b; a=1ll*a*a%mod,b>>=1)
    13         if (b & 1) res=1ll*res*a%mod;
    14     return res;
    15 }
    16 
    17 int calc(int n,int m){
    18     if (n==m) return d[n];
    19     if (n==1) return ksm(2,m);
    20     if (n==2) return 4ll*ksm(3,m-1)%mod;
    21     if (n==3) return 112ll*ksm(3,m-3)%mod;
    22     return ((3ll*d[n]-3ll*ksm(2,n))*ksm(3,m-n-1)%mod+mod)%mod;
    23 }
    24 
    25 int main(){
    26     freopen("game.in","r",stdin);
    27     freopen("game.out","w",stdout);
    28     scanf("%d%d",&n,&m);
    29     if (n>m) swap(n,m);
    30     printf("%d
    ",calc(n,m));
    31     return 0;
    32 }
    game(100)

    D2T3:保卫王国

    难度和知识点几乎脱离NOIP范围的一道题,有倍增做法但感觉难度很大,这里直讲动态DP做法。

    算法一:n,m<=2000就是经典的《没有上司的舞会》,每次修改暴力重做一次树形DP即可。

    期望得分:50

    复杂度:$O(n^2)$

    算法二:A1,A2保证是链,且修改只涉及到一个位置或两个相邻位置,记录前缀后缀即可。

    期望得分:20

    复杂度:$O(n)$

    算法三:B1深度不超过100,可以发现每次修改只会涉及到它到根的链上的DP值,于是可以优化复杂度。

    期望得分:8

    复杂度:$O(100n)$

    以上部分分通过数据分治可以获得72分。

      1 #include<cstdio>
      2 #include<cstring>
      3 #include<iostream>
      4 #include<algorithm>
      5 #include<vector>
      6 using namespace std;
      7 typedef long long ll;
      8 const int N=1e5+7;
      9 int n,m,tp[N],p[N],fa[N];
     10 ll f[N][2],g[N][2];
     11 vector<int>G[N];
     12 char typ[3];
     13 void dp(int u,int ff)
     14 {
     15     f[u][0]=0;f[u][1]=p[u];
     16     for(int i=0;i<(int)G[u].size();i++)
     17     if(G[u][i]!=ff)
     18     {
     19         dp(G[u][i],u);
     20         f[u][0]+=f[G[u][i]][1];
     21         if(f[u][0]>1e17)f[u][0]=1e17;
     22         f[u][1]+=min(f[G[u][i]][0],f[G[u][i]][1]);
     23         if(f[u][1]>1e17)f[u][1]=1e17;
     24     }
     25     if(!tp[u])f[u][1]=1e17;
     26     if(tp[u]==1)f[u][0]=1e17;
     27 }
     28 void dfs0(int u)
     29 {
     30     for(int i=0;i<(int)G[u].size();i++)
     31         if(G[u][i]!=fa[u])fa[G[u][i]]=u,dfs0(G[u][i]);
     32 }
     33 void modify(int u,int son)
     34 {
     35     g[u][0]=f[u][0],g[u][1]=f[u][1];
     36     if(g[u][0]!=1e17)
     37     {
     38         g[u][0]-=f[son][1];
     39         g[u][0]+=g[son][1];
     40         if(g[u][0]>1e17)g[u][0]=1e17;
     41     }
     42     if(g[u][1]!=1e17)
     43     {
     44         g[u][1]-=min(f[son][0],f[son][1]);
     45         g[u][1]+=min(g[son][0],g[son][1]);
     46         if(g[u][1]>1e17)g[u][1]=1e17;
     47     }
     48     if(u!=1)modify(fa[u],u);
     49 }
     50 int main()
     51 {
     52     scanf("%d%d%s",&n,&m,typ);
     53     for(int i=1;i<=n;i++)scanf("%d",&p[i]);
     54     if(n<=2000&&m<=2000)
     55     {
     56         memset(tp,-1,sizeof tp);
     57         for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
     58         while(m--)
     59         {
     60             int a,x,b,y;
     61             scanf("%d%d%d%d",&a,&x,&b,&y);
     62             tp[a]=x,tp[b]=y;
     63             dp(1,0);
     64             tp[a]=tp[b]=-1;
     65             ll ans=min(f[1][0],f[1][1]);
     66             if(ans==1e17)puts("-1");
     67             else printf("%lld
    ",ans);
     68         }
     69         return 0;
     70     }
     71     if((typ[0]=='B'||typ[0]=='C')&&typ[1]=='1')
     72     {
     73         for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
     74         dfs0(1);
     75         memset(tp,-1,sizeof tp);
     76         dp(1,0);
     77         while(m--)
     78         {
     79             int b,y;
     80             scanf("%*d%*d%d%d",&b,&y);
     81             g[b][y]=f[b][y];g[b][y^1]=1e17;
     82             if(b!=1)modify(fa[b],b);
     83             if(g[1][1]==1e17)puts("-1");
     84             else printf("%lld
    ",g[1][1]);
     85         }
     86         return 0;
     87     }
     88     if(typ[0]=='A')for(int i=1;i<n;i++)scanf("%*d%*d");
     89     if(typ[0]=='A'&&typ[1]=='1')
     90     {
     91         f[n][0]=0,f[n][1]=p[n];
     92         for(int i=n-1;i;i--)f[i][0]=f[i+1][1],f[i][1]=min(f[i+1][0],f[i+1][1])+p[i];
     93         g[1][0]=1e17,g[1][1]=p[1];
     94         for(int i=2;i<=n;i++)g[i][0]=g[i-1][1],g[i][1]=min(g[i-1][0],g[i-1][1])+p[i];
     95         while(m--)
     96         {
     97             int b,y;scanf("%*d%*d%d%d",&b,&y);
     98             ll ans;
     99             if(!y)ans=f[b+1][1]+g[b-1][1];
    100                 else ans=min(f[b+1][0],f[b+1][1])+min(g[b-1][0],g[b-1][1])+p[b];
    101             printf("%lld
    ",ans);
    102         }
    103         return 0;
    104     }
    105     if(typ[0]=='A'&&typ[1]=='2')
    106     {
    107         f[n][0]=0,f[n][1]=p[n];
    108         for(int i=n-1;i;i--)f[i][0]=f[i+1][1],f[i][1]=min(f[i+1][0],f[i+1][1])+p[i];
    109         g[1][0]=0,g[1][1]=p[1];
    110         for(int i=2;i<=n;i++)g[i][0]=g[i-1][1],g[i][1]=min(g[i-1][0],g[i-1][1])+p[i];
    111         while(m--)
    112         {
    113             int a,x,b,y;scanf("%d%d%d%d",&a,&x,&b,&y);
    114             if(a>b)swap(a,b),swap(x,y);
    115             if(!x&&!y){puts("-1");continue;}
    116             ll ans=f[b][y]+g[a][x];
    117             printf("%lld
    ",ans);
    118         }
    119         return 0;
    120     }
    121     while(m--)puts("-1");
    122     return 0;
    123 }
    72分代码(From CTF)

    算法四:树上带修改最小权覆盖问题。最小权覆盖=全集-最大权独立集。通过设inf和-inf控制选与不选。

    带修改最大权独立集问题有经典的动态DP做法,通过链分治、矩乘和线段树加速。

    树剖$O(nlog^2 n)$且常数极大,理论上极限数据可以卡到20s以上。

    使用LCT或全局平衡二叉树可以做到$O(nlog n)$。

    期望得分:100

    复杂度:$O(nlog n)$

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define ls (x<<1)
     5 #define rs (ls|1)
     6 #define lson ls,L,mid
     7 #define rson rs,mid+1,R
     8 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     9 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
    10 typedef long long ll;
    11 using namespace std;
    12 
    13 const int N=100010;
    14 const ll inf=1e12;
    15 ll sm,a[N],f[N][2];
    16 int n,m,u,v,s1,s2,q[N],top[N],L[N],R[N],id[N],son[N],sz[N],fa[N];
    17 int cnt,tot,tim,h[N],to[N<<1],nxt[N<<1];
    18 inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
    19 
    20 struct Mat{
    21     ll g[2][2];
    22     Mat(){ g[0][0]=g[0][1]=g[1][0]=g[1][1]=0; }
    23 }val[N],T[N<<2];
    24 
    25 Mat operator *(const Mat &a,const Mat &b){
    26     Mat c;
    27     rep(i,0,1) rep(j,0,1) rep(k,0,1) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
    28     return c;
    29 }
    30 
    31 void dfs(int x){
    32     sz[x]=1; f[x][1]=a[x];
    33     For(i,x) if ((k=to[i])!=fa[x]){
    34         fa[k]=x; dfs(k); sz[x]+=sz[k];
    35         f[x][0]+=max(f[k][1],f[k][0]); f[x][1]+=f[k][0];
    36         if (sz[k]>sz[son[x]]) son[x]=k;
    37     }
    38     ll g0=0,g1=a[x];
    39     For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]) g0+=max(f[k][0],f[k][1]),g1+=f[k][0];
    40     val[x].g[0][0]=val[x].g[0][1]=g0; val[x].g[1][0]=g1;
    41 }
    42 
    43 void build(int x,int L,int R){
    44     if (L==R){ T[x]=val[id[L]]; return; }
    45     int mid=(L+R)>>1;
    46     build(lson); build(rson);
    47     T[x]=T[ls]*T[rs];
    48 }
    49 
    50 void mdf(int x,int L,int R,int pos){
    51     if (L==R){ T[x]=val[id[L]]; return; }
    52     int mid=(L+R)>>1;
    53     if (pos<=mid) mdf(lson,pos); else mdf(rson,pos);
    54     T[x]=T[ls]*T[rs];
    55 }
    56 
    57 Mat que(int x,int L,int R,int l,int r){
    58     if (L==l && r==R) return T[x];
    59     int mid=(L+R)>>1;
    60     if (r<=mid) return que(lson,l,r);
    61     else if (l>mid) return que(rson,l,r);
    62         else return que(lson,l,mid)*que(rson,mid+1,r);
    63 }
    64 
    65 void solve(int x,ll k){
    66     sm+=k-a[x]; val[x].g[1][0]+=k-a[x]; a[x]=k;
    67     Mat od,nw;
    68     while (x){
    69         od=que(1,1,n,L[top[x]],R[top[x]]); mdf(1,1,n,L[x]); 
    70         nw=que(1,1,n,L[top[x]],R[top[x]]); x=fa[top[x]];
    71         val[x].g[0][1]=val[x].g[0][0]+=max(nw.g[0][0],nw.g[1][0])-max(od.g[0][0],od.g[1][0]);
    72         val[x].g[1][0]+=nw.g[0][0]-od.g[0][0];
    73     }
    74 }
    75 
    76 int main(){
    77     freopen("defense.in","r",stdin);
    78     freopen("defense.out","w",stdout);
    79     scanf("%d%d%*s",&n,&m); q[1]=1; tot=1;
    80     rep(i,1,n) scanf("%lld",&a[i]),sm+=a[i];
    81     rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    82     dfs(1);
    83     rep(x,1,n) For(i,x) if ((k=to[i])!=fa[x]) q[++tot]=k;
    84     rep(i,1,n) if (!top[u=q[i]]){
    85         for (int k=u; k; k=son[k]) id[L[k]=++tim]=k,top[k]=u;
    86         R[u]=tim;
    87     }
    88     build(1,1,n);
    89     rep(i,1,m){
    90         scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
    91         if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
    92         if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
    93         if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
    94         Mat ans=que(1,1,n,L[1],R[1]); ll res=sm-max(ans.g[0][0],ans.g[1][0]);
    95         res=(res%inf+inf)%inf; printf("%lld
    ",res);
    96         solve(u,t1); solve(v,t2);
    97     }
    98     return 0;
    99 }
    defense(树剖)
     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define ls (x<<1)
     5 #define rs (ls|1)
     6 #define lson ls,L,mid
     7 #define rson rs,mid+1,R
     8 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
     9 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
    10 typedef long long ll;
    11 using namespace std;
    12 
    13 const int N=100010;
    14 const ll inf=1e12;
    15 ll sm,a[N],s[N][2];
    16 int n,m,u,v,s1,s2,top[N],L[N],R[N],id[N],son[N],sz[N],f[N],fa[N],ch[N][2];
    17 int cnt,h[N],to[N<<1],nxt[N<<1];
    18 inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
    19 
    20 struct Mat{
    21     ll g[2][2];
    22     Mat(){ g[0][0]=g[0][1]=g[1][0]=g[1][1]=0; }
    23 }val[N],T[N];
    24 
    25 Mat operator *(const Mat &a,const Mat &b){
    26     Mat c;
    27     rep(i,0,1) rep(j,0,1) rep(k,0,1) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
    28     return c;
    29 }
    30 
    31 void dfs(int x){
    32     s[x][1]=a[x];
    33     For(i,x) if ((k=to[i])!=f[x]){
    34         fa[k]=x; f[k]=x; dfs(k);
    35         s[x][0]+=max(s[k][1],s[k][0]); s[x][1]+=s[k][0];
    36     }
    37     ll g0=0,g1=a[x];
    38     For(i,x) if ((k=to[i])!=f[x] && k!=son[x]) g0+=max(s[k][0],s[k][1]),g1+=s[k][0];
    39     val[x].g[0][0]=val[x].g[0][1]=g0; val[x].g[1][0]=g1; T[x]=val[x];
    40 }
    41 
    42 bool isroot(int x){ return (!f[x]) || (ch[f[x]][0]!=x && ch[f[x]][1]!=x); }
    43 
    44 void upd(int x){
    45     if (ch[x][0]) T[x]=T[ch[x][0]]*val[x]; else T[x]=val[x];
    46     if (ch[x][1]) T[x]=T[x]*T[ch[x][1]];
    47 }
    48 
    49 void rot(int x){
    50     int y=f[x],z=f[y],w=(ch[y][1]==x);
    51     if (!isroot(y)) ch[z][ch[z][1]==y]=x;
    52     f[x]=z; f[y]=x; f[ch[x][w^1]]=y;
    53     ch[y][w]=ch[x][w^1]; ch[x][w^1]=y; upd(y);
    54 }
    55 
    56 void splay(int x){
    57     while (!isroot(x)){
    58         int y=f[x],z=f[y];
    59         if (!isroot(y)) ((ch[z][1]==y)^(ch[y][1]==x))?rot(x):rot(y);
    60         rot(x);
    61     }
    62     upd(x);
    63 }
    64 
    65 void access(int x){
    66     for (int y=0; x; y=x,x=f[x]){
    67         splay(x); Mat od,nw;
    68         if (ch[x][1]) nw=T[ch[x][1]];
    69         ch[x][1]=y;
    70         if (ch[x][1]) od=T[ch[x][1]];
    71         val[x].g[0][1]=val[x].g[0][0]+=max(nw.g[0][0],nw.g[1][0])-max(od.g[0][0],od.g[1][0]);
    72         val[x].g[1][0]+=nw.g[0][0]-od.g[0][0]; upd(x);
    73     }
    74 }
    75 
    76 void solve(int x,ll k){ access(x); splay(x); val[x].g[1][0]+=k-a[x]; a[x]=k; upd(x); }
    77 
    78 int main(){
    79     freopen("defense.in","r",stdin);
    80     freopen("defense.out","w",stdout);
    81     scanf("%d%d%*s",&n,&m);
    82     rep(i,1,n) scanf("%lld",&a[i]),sm+=a[i];
    83     rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    84     dfs(1);
    85     rep(i,1,m){
    86         scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
    87         if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
    88         if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
    89         if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
    90         splay(1); ll res=sm-max(T[1].g[0][0],T[1].g[1][0]);
    91         res=(res%inf+inf)%inf; printf("%lld
    ",res);
    92         solve(u,t1); solve(v,t2);
    93     }
    94     return 0;
    95 }
    defense(LCT)
  • 相关阅读:
    [UGUI]事件机制
    [C#]CompareTo
    [Unity算法]交换排序(二):快速排序
    [Unity算法]交换排序(一):冒泡排序
    [Lua]string(二):string.sub处理中文
    [Lua]string(一):string库
    [Unity热更新]LuaFramework14.打包、加载和卸载策略
    [Unity热更新]LuaFramework13.事件派发
    数仓day01
    大数据学习day39----数据仓库02------1. log4j 2. 父子maven工程(子spring项目的创建)3.项目开发(埋点日志预处理-json数据解析、清洗过滤、数据集成实现、uid回补)
  • 原文地址:https://www.cnblogs.com/HocRiser/p/9977020.html
Copyright © 2011-2022 走看看