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\n",ans); 19 return 0; 20 }
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\n",ans); 25 } 26 return 0; 27 }
D1T3:賽道修建
十分套路的一道題。
算法一:m=1的情況就是要找一條直徑。
期望得分:20
復雜度:$O(n)$
算法二:ai=1是一個菊花圖,二分答案后盡量匹配出最多的長度>=mid的鏈。這個貪心或再套一層二分答案均可。
期望得分:20
復雜度:$O(n\log n)$
算法三:bi=ai+1是一條鏈,二分答案后貪心選取即可。若當前鏈長度已>=mid則斷開,計數器+1。
期望得分:20
復雜度:$O(n\log n)$
以上部分通過數據分治可以得到55分。
算法四:分支不超過3,只要找到一個度為1的點作為根就是一棵二叉樹。在樹上做簡單DP即可,具體可見滿分做法。
期望得分:55
復雜度:$O(n\log 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(n\log^2 n)$或$O(n\log 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\n",ans); 44 return 0; 45 }
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 }
或者直接找到環,只刪環上的邊。這在環很小的時候效果極佳。

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 }
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\n",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\n",((f[m][0]+f[m][1])%mod+(f[m][2]+f[m][3])%mod)%mod); 24 return 0; 25 }
算法三: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\n",ans); 27 return 0; 28 }
算法四:根據結論一和結論二給暴搜剪枝,可以在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 }
以上部分分通過數據分治可以得到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\n",calc(n,m)); 31 return 0; 32 }
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\n",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\n",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\n",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\n",ans); 118 } 119 return 0; 120 } 121 while(m--)puts("-1"); 122 return 0; 123 }
算法四:樹上帶修改最小權覆蓋問題。最小權覆蓋=全集-最大權獨立集。通過設inf和-inf控制選與不選。
帶修改最大權獨立集問題有經典的動態DP做法,通過鏈分治、矩乘和線段樹加速。
樹剖$O(n\log^2 n)$且常數極大,理論上極限數據可以卡到20s以上。
使用LCT或全局平衡二叉樹可以做到$O(n\log n)$。
期望得分:100
復雜度:$O(n\log 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\n",res); 96 solve(u,t1); solve(v,t2); 97 } 98 return 0; 99 }

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\n",res); 92 solve(u,t1); solve(v,t2); 93 } 94 return 0; 95 }