AtCoder Regular Contest 097
C - K-th Substring
题意:求一个长度小于等于5000的字符串的第K小子串,相同子串算一个。
K<=5。
分析:这不是弦论那道题吗。。
观察到K<=5,我们把所有长度小于等于5的子串拿出来去重再排个序即可。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 5050 int K,n; struct A { char s[10]; bool operator < (const A &x) const { return strcmp(s,x.s)<0; } bool operator == (const A &x) const { return strcmp(s,x.s)==0; } }a[N<<3]; char s[N]; int main() { int cnt=0; scanf("%s%d",s,&K); int i,j,k;n=strlen(s); for(i=0;i<n;i++) { for(j=1;j<=5&&i+j-1<n;j++) { ++cnt; for(k=i;k<=i+j-1;k++) a[cnt].s[k-i]=s[k]; } } sort(a+1,a+cnt+1); cnt=unique(a+1,a+cnt+1)-a-1; printf("%s ",a[K].s); }
D - Equals
题意:给出一个n的排列,m条信息,每个信息(x,y)表示可以交换位置为x和y的两个数。
可以进行若干次操作,求操作后最多有多少ai=i。
分析:把信息当成边,可以发现一个连通块里的点可以乱窜,直接判断每个位置和对应的值在不在一个连通块内即可。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 100050 int fa[N],a[N],n,m,ans; int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);} int main() { int i; scanf("%d%d",&n,&m); int x,y; for(i=1;i<=n;i++) scanf("%d",&a[i]),fa[i]=i; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); int dx=find(x),dy=find(y); if(dx!=dy) fa[dx]=dy; } for(i=1;i<=n;i++) if(find(a[i])==find(i)) ans++; printf("%d ",ans); }
E - Sorted and Sorted
题意:有n个白球和n个黑球,编号为1到n。现在让你每次交换相邻的两个球,使得最后黑球编号递增,白球编号递增。
分析:贪心的想每次肯定从编号小往前移动,但每次可以移动黑球也可以移动白球。
于是设f[i][j]表示白球已经放了前i个,黑球已经放了前j个的最小移动次数。
转移f[i][j]+编号为i+1的白球前面有多少编号大于i+1的白球,有多少编号大于j的黑球->f[i+1][j]
f[i][j]+编号为j+1的黑球前面有多少编号大于i的白球,有多少编号大于j+1的黑球->f[i][j+1]
O(n^2)预处理出来第i个球前面都多少大于j的白/黑球即可。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 2050 int f[N][N],opt[N<<1],a[N<<1],cw[N<<1][N],cb[N<<1][N],pos[2][N],n; char str[10]; int main() { scanf("%d",&n); int i,j; for(i=1;i<=(n<<1);i++) { scanf("%s%d",str,&a[i]); opt[i]=(str[0]=='B'); pos[opt[i]][a[i]]=i; } for(i=1;i<=(n<<1);i++) { for(j=0;j<=n;j++) { cw[i][j]=cw[i-1][j]; cb[i][j]=cb[i-1][j]; if(a[i-1]>j) opt[i-1]?cb[i][j]++:cw[i][j]++; } } memset(f,0x3f,sizeof(f)); f[0][0]=0; for(i=0;i<=n;i++) { for(j=0;j<=n;j++) { int u=pos[0][i+1],v=pos[1][j+1]; f[i+1][j]=min(f[i+1][j],f[i][j]+cw[u][i+1]+cb[u][j]); f[i][j+1]=min(f[i][j+1],f[i][j]+cw[v][i]+cb[v][j+1]); } } printf("%d ",f[n][n]); }
F - Monochrome Cat
题意:有一棵树,每个结点是黑色或白色,你可以选择一个起点开始走,每秒有两种选择。
1.走向一个相邻的结点,并翻转该结点的颜色。
2.在原地翻转结点的颜色。
分析:任选一个白色结点当做根。显然对于全黑色的子树不会被遍历到,于是删掉这样的。
树上所有叶子结点都变成白色的了。
考虑起点和终点相同的情况。首先每条边要走两次,并且对于度数+颜色%2==0的点还要花费1时间来翻转成黑色。
那么考虑起点和终点不一样的情况,还是起点、终点都是叶子。
走的路径相当于少了一条链+终点,考虑链上原来的黑点没有减少答案,原来的白点减少了2(遍历一次翻转一次)。相当于每个点赋一个点权。
现在要求一条起点叶子终点叶子的路径,路径上点权和最大。
设f[i]表示i到叶子的最大和,g[i]表示i到叶子的父亲的最大和(叶子是终点)。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 100050 char str[N]; int head[N],to[N<<1],nxt[N<<1],a[N],c[N],cnt,siz[N],f[N],g[N],si[N],d[N],n,ans; inline void add(int u,int v) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; } void dfs(int x,int y) { int i;siz[x]=1; si[x]=c[x]; for(i=head[x];i;i=nxt[i]) { if(to[i]!=y) { dfs(to[i],x); siz[x]+=siz[to[i]]; si[x]+=si[to[i]]; if(si[to[i]]!=siz[to[i]]) d[to[i]]++,d[x]++; } } } void dfs2(int x,int y) { int i; int mx1=0,mx2=0; if(d[x]==1&&y) {f[x]=a[x],g[x]=-1<<30; return ;} for(i=head[x];i;i=nxt[i]) { if(to[i]!=y&&siz[to[i]]!=si[to[i]]) { dfs2(to[i],x); ans=max(ans,f[to[i]]+mx2+a[x]); ans=max(ans,g[to[i]]+mx1+a[x]); mx1=max(mx1,f[to[i]]); mx2=max(mx2,g[to[i]]); } } f[x]=mx1+a[x]; g[x]=mx2+a[x]; } int main() { scanf("%d",&n); int i,x,y; for(i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } scanf("%s",str+1); int rt=0; for(i=1;i<=n;i++) { c[i]=(str[i]=='B'); if(!c[i]) rt=i; } if(rt) dfs(rt,0); else { puts("0"); return 0; } int sum=0; for(i=1;i<=n;i++) { if(siz[i]==si[i]) continue; sum+=d[i]; if((d[i]+c[i])%2==0) sum++,a[i]=2; } dfs2(rt,0); printf("%d ",sum-ans); }