给定一棵树,每条边有边权,你能够砍掉任意一条边,再将其添上(权值不变),使其还保持一棵树的形态,求这样操做过的新树的最小直径。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)))); } }
代码被格式化了,难受。