給定一棵樹,每條邊有邊權,你能夠砍掉任意一條邊,再將其添上(權值不變),使其還保持一棵樹的形態,求這樣操做過的新樹的最小直徑。c++
先找出原樹的直徑,那麼切掉的邊必定在其直徑上,枚舉直徑的每條邊,將其割掉,原樹被分爲兩棵樹。git
求出兩個連通塊的直徑,做爲新樹最長直徑的備選方案。數組
考慮如何插入邊。spa
想要使插入後樹的直徑最小,那麼首先邊的兩個端點要位於兩棵樹的直徑上,不然直徑可能會通過兩條直徑的一部分,而後在通過連邊聯通的那部分互相聯通,合二爲一成直徑,若是將端點設置在直徑上,無疑會減小聯通上的這部分花費,直接將兩條直徑聯通,所以會更優。code
而後考慮插到直徑的哪一個位置,最終樹的直徑的備選方案必定會是兩邊端點的兩側中最長的一部分,再加上這條邊的長度,所以使兩條直徑兩端長的那方儘量小就能夠了,相似於找到其直徑的中點或最接近其中點的地方。ci
最後誕生的新樹的直徑就是兩個直徑和兩個直徑的一半加上新的連邊,三者的最大值,再從最大值中選出最小值即爲答案。get
枚舉刪那條邊 \(O(n)\) ,計算兩棵樹的直徑 \(O(n)\) ,總計時間複雜度 \(O(n^2)\)。qt
#include<bits/stdc++.h> using namespace std; const int N=5e3+8; int n; int fr[N],to[N<<1],nxt[N<<1],too=1,w[N<<1]; bool cx[N<<1]; int dep[N],t1,t2; int tt1,tt2; int flag=0; int d[N],tot=0,e[N]; int dd[N],ttot=0,ee[N]; int D,dh; void add(int x,int y,int z) { to[++too]=y; nxt[too]=fr[x]; fr[x]=too; w[too]=z; } void dfs1(int x,int fa) { if(flag==0) tt1=dep[x]>dep[tt1]?x:tt1; else tt2=dep[x]>dep[tt2]?x:tt2; for(int i=fr[x];i;i=nxt[i]) { if(cx[i]) continue; int y=to[i]; if(y==fa) continue; dep[y]=dep[x]+w[i]; dfs1(y,x); } } bool dfs2(int x,int fa) { if(x==tt2) return 1; for(int i=fr[x];i;i=nxt[i]) { if(cx[i]) continue; int y=to[i]; if(y==fa) continue; if(y==tt2||dfs2(y,x)) { d[++tot]=y; e[tot]=i; return 1; } } return 0; } void work(int rt) { dep[rt]=0; flag=0; tot=0; tt1=tt2=rt; dfs1(rt,0); flag=1; dep[tt1]=0; dfs1(tt1,0); dfs2(tt1,0); if(tt1==tt2) { D=dh=0; return; } D=dep[tt2]; dh=0x7fffffff; d[++tot]=tt1; for(int i=1;i<=tot;i++) dh=min(max(dep[d[i]],dep[tt2]-dep[d[i]]),dh); } int main() { cin>>n; int x,y,z; for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&z); add(x,y,z);add(y,x,z); } dfs1(1,0); flag=1; t1=tt1; dep[t1]=0; dfs1(t1,0); dfs2(t1,0); t2=tt2; ttot=tot; for(int i=1;i<=tot;i++) dd[i]=d[i],ee[i]=e[i]; int ans=0x7fffffff; for(int i=1;i<=ttot;i++) { int num=0; cx[ee[i]]=1; cx[ee[i]^1]=1; work(t1); int hp=dh; num=D; work(t2); num=max(dh+hp+w[ee[i]],max(D,num)); ans=min(ans,num); cx[ee[i]]=0; cx[ee[i]^1]=0; } cout<<ans; }
求全部子段和的異或和。it
先求出前綴和,考慮每個前綴和。ast
對於一個前綴和,即要把它與以前每一個前綴和的差別或進答案中,拆位考慮,對於每一位,若是此位爲 \(1\),那麼與以前的這位爲 \(0\) ,餘下右部分比其小(不借位),和以前這位爲 \(1\),餘下右部分比起大(要借位)的前綴和作差,此位最終爲 \(1\),若是此位爲 \(0\) 狀況類似。
所以只要快速算出前幾個前綴和中本位爲 \(1\) 或 \(0\) 且比如今這個前綴和此位以右部分大的或小的數有幾個便可,可用樹狀數組,開三維,分別爲位數,大小(普通樹狀數組的那維)和最高位是 \(0\) 仍是 \(1\)。
#include<bits/stdc++.h> using namespace std; const int N=1e6+8; int n; int s[N]; int c[25][N<<2][2]; int ans=0; int t1=0,t0=0; int read() { char c=getchar(); while(c>'9'||c<'0') c=getchar(); int x=0; while(c>='0'&&c<='9') { x=(x<<3)+(x<<1); x+=c-'0'; c=getchar(); } return x; } int ask(int t,int x,int op) { int num=0; while(x) { num+=c[t][x][op]; x-=(x&-x); } return num; } void ad(int t,int x,int op) { while(x<=(1<<t)) { c[t][x][op]++; x+=(x&-x); } } int main() { cin>>n; for(int i=1;i<=n;i++) s[i]=read(),s[i]+=s[i-1]; for(int i=1;i<=19;i++) ad(i,1,0); t0=1; for(int i=1;i<=n;i++) { int p=0; if((s[i]&1)&&(t0&1)) ans^=1; if(!(s[i]&1)&&(t1&1)) ans^=1; if(s[i]&1) t1++,p++; else t0++; for(int j=1;j<=19;j++) { if(s[i]&(1<<j)) { int num=0; num+=ask(j,p+1,0); num+=ask(j,(1<<j),1)-ask(j,p+1,1); if(num&1) ans^=(1<<j); ad(j,p+1,1); p+=(1<<j); } else { int num=0; num+=ask(j,p+1,1); num+=ask(j,(1<<j),0)-ask(j,p+1,0); if(num&1) ans^=(1<<j); ad(j,p+1,0); } } } cout<<ans; }
恭喜本題跑到最優解第三。
看起來用玄學時間複雜度有時比暴力更好一點。
給定 \(a\) 串和 \(b\) 串,求 \(a\) 串中修改不超過三處便可變爲 \(b\) 串的子串數量。
創建後綴自動機,並在其上\(dfs\) 便可,時間複雜度玄學,能過。
別忘了創建自動機後先算出每一個串的出現次數,深搜到答案時加上本串的出現次數。
#include<bits/stdc++.h> using namespace std; const int N=4e5+8; struct sam{ int ch[4]; int fa,len; } a[N]; char s[N],ss[N]; int ls; int tot=1,last=1; int cnt[N]; int ans; int rev[234]; int fr[N],to[N],nxt[N],too=0; void add(int x,int y) { to[++too]=y; nxt[too]=fr[x]; fr[x]=too; } void ad(int x) { int p=last,k=last=++tot;cnt[k]=1; a[k].len=a[p].len+1; for(;p&&!a[p].ch[x];p=a[p].fa) a[p].ch[x]=k; if(!p) a[k].fa=1; else{ int t1=a[p].ch[x]; if(a[t1].len==a[p].len+1) a[k].fa=t1; else{ int t2=++tot; a[t2]=a[t1]; a[t2].len=a[p].len+1; a[k].fa=a[t1].fa=t2; for(;p&&a[p].ch[x]==t1;p=a[p].fa) a[p].ch[x]=t2; } } } void dfs(int k,int lt,int p) { if(lt==ls+1){ if(p<=3) ans+=cnt[k]; return; } if(p>3) return; for(int i=0;i<4;i++) { if(a[k].ch[i]) dfs(a[k].ch[i],lt+1,p+(!(i==rev[ss[lt]]))); } } void dfs1(int x) { for(int i=fr[x];i;i=nxt[i]) dfs1(to[i]),cnt[x]+=cnt[to[i]]; } int main() { int T; cin>>T; rev['A']=0,rev['C']=1,rev['G']=2,rev['T']=3; while(T--) { last=tot=1; memset(a,0,sizeof(a)); memset(cnt,0,sizeof(cnt)); scanf("%s",s+1); scanf("%s",ss+1); int len=strlen(s+1); ls=strlen(ss+1); for(int i=1;i<=len;i++) ad(rev[s[i]]); memset(fr,0,sizeof(fr)); too=0; for(int i=2;i<=tot;i++) add(a[i].fa,i); dfs1(1); ans=0; dfs(1,1,0); printf("%d\n",ans); } }
機器人有三種行爲: 停在原地,去下一個相鄰的城市,自爆。它每一秒都會隨機觸發一種行爲。給定圖和時間,輸出可樂機器人的行爲方案數。
在得知本題爲矩乘以後,本題作法就一目瞭然了,創建出鄰接矩陣爲每次乘的矩陣,多創建個零號節點做爲自爆點,將每一個節點都連向它,作矩陣快速冪就好了。
#include<bits/stdc++.h> using namespace std; struct sqt{ int a[101][101]; sqt(){memset(a,0,sizeof(a));} }; int mod=2017; int n,m; sqt operator * (const sqt &aa,const sqt &bb) { sqt c; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) { for(int k=0;k<=n;k++) c.a[i][j]=(c.a[i][j]+aa.a[k][j]*bb.a[i][k])%2017; } return c; } sqt s; sqt ans; void qm(int t) { sqt p=s; for(int i=0;i<=n;i++) ans.a[i][i]=1; while(t) { if(t&1) ans=ans*p; p=p*p; t>>=1; } } int main() { cin>>n>>m; int x,y; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); s.a[x][y]=s.a[y][x]=1; } for(int i=0;i<=n;i++) s.a[0][i]=1,s.a[i][i]=1; int t; cin>>t; qm(t); int aans=0; for(int i=0;i<=n;i++) aans=(aans+ans.a[i][1])%mod; cout<<aans; }
原題數據弱,暴力可跑過。。
給一個長度爲 n 的序列,每一個位置有權值和價值,若是兩個數爲逆序對,便可產生這兩個位置價值和的價值,每次會交換兩個數(包括權值和價值),求每次交換後的序列的總價值。
和這題很像:P1975 [國家集訓隊]排隊
這兩題都被我用暴力跑過(逃~),不過排隊那題暴力爲正解。
原序列用樹狀數組求逆序對,接下來暴力枚舉交換位置之間的數計算其形成影響,時間複雜度\(O(n logn+nm)\).
#include<bits/stdc++.h> using namespace std; int mod=1e9+7; const int N=5e4+7; int n,m,l,r; int a[N],v[N],d[N],c[N]; long long v1,v2,ans; inline void rd(int &X){ X=0;char ch=0; while(!isdigit(ch))ch=getchar(); while( isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); } void ad(int x,int v) { while(x) { c[x]+=v,d[x]++; x-=x&-x; } } void ask(int x) { v1=v2=0; while(x<=n) { v1+=c[x],v2+=d[x]; x+=x&-x; } } signed main() { rd(n);rd(m); for(int i=1;i<=n;++i) rd(a[i]),rd(v[i]),ask(a[i]),ad(a[i],v[i]),ans+=v1+v2*v[i]; while(m--) { rd(l);rd(r); if(l>r) swap(l,r); int i=l+1; while(i<r) { if(a[i]>a[l]) ans+=v[i]+v[l]; if(a[i]<a[l]) ans-=v[i]+v[l]; if(a[i]>a[r]) ans-=v[i]+v[r]; if(a[i]<a[r]) ans+=v[i]+v[r]; ++i; } if(a[l]>a[r]) ans-=v[l]+v[r]; if(a[l]<a[r]) ans+=v[l]+v[r]; swap(a[l],a[r]);swap(v[l],v[r]); printf("%lld\n",ans=(ans%mod+mod)%mod); } }
不過這解法明顯不優,但我不會樹套樹。。
可是能夠用分塊,考慮每次修改,兩端暴力,對於每一個塊內,只要先排好序求個前綴和,二分找到分界的位置計算貢獻便可,但代碼沒打。。暴力能過誰還打分塊。
之後交換仍是用 \(swap\) 好了,四個異或不靠譜。
歷史遺留下次再說。。(逃~~
題意過長不贅述。
比較噁心的但不算太噁心的模擬題,設兩我的牌號分別爲 \(0\),\(1\) 和 \(2\),\(3\),由於只有兩張牌,所以第一張牌出完狀況就固定了,因此無非就四種狀況:\(02,03,12,13\)。都算一邊再自行合適的取 \(min\) 和 \(max\)就好了。
#include <bits/stdc++.h> using namespace std; int a[5]; char s[7]; int get(char c) { if (c <= '9' && c >= '2') return c - '0'; else if (c == 'T') return 10; else if (c == 'A') return 1; else if (c == 'K') return 13; else if (c == 'Q') return 12; else if (c == 'J') return 11; } bool pd(int x, int y) { int a1 = a[x]; int a2 = a[y]; if (a1 == 1) a1 = 14; if (a2 == 1) a2 = 14; return a1 >= a2; } int work(int t1, int t2) { int num = 0; if (pd(t1, t2)) { num += a[t1]; t1 ^= 1, t2 ^= 1; if (pd(t1, t2)) num += a[t1]; else num -= a[t2]; } else { num -= a[t2]; t1 ^= 1, t2 ^= 1; if (pd(t2, t1)) num -= a[t2]; else num += a[t1]; } return num; } int main() { freopen("card.in", "r", stdin); freopen("card.out", "w", stdout); int T; cin >> T; while (T--) { for (int i = 0; i < 4; i++) scanf("%s", s), a[i] = get(s[0]); printf("%d\n", max(min(work(0, 2), work(0, 3)), min(work(1, 2), work(1, 3)))); } }
代碼被格式化了,難受。