AtCoder Regular Contest 097

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);
}
原文地址:https://www.cnblogs.com/suika/p/9252272.html