Codeforces Round #551 (Div. 2) 题解

BG


周六超多比赛,为了上分还是只打了cf。。本来想上橙的。。甚至第一次赛前打好了各种模板。。。
被hack到心态崩了

A


很显然我们记r[x]表示时刻x的任意一个bus,跑一个2e5*n的暴力就可以了
挂了是因为只跑了1e5,怀疑人生

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long long LL;

const int N=200005;

int v[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int main(void) {
	int n=read(),t=read();
	rep(i,1,n) {
		int s=read(),d=read();
		for (int j=s;j<=2e5;j+=d) v[j]=i;
	}
	rep(i,t,2e5) if (v[i]) {
		printf("%d\n", v[i]);
		return 0;
	}
	return 0;
}

B


没啥好说的,f[i,j]=min(a[i],b[j])

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long long LL;

const int INF=0x3f3f3f3f;
const int N=205;

int a[N],b[N],c[N][N],d[N][N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int main(void) {
	int n=read(),m=read(),h=read();
	rep(i,1,m) a[i]=read();
	rep(i,1,n) b[i]=read();
	rep(i,1,n) rep(j,1,m) c[i][j]=read();
	rep(i,1,n) rep(j,1,m) {
		if (!c[i][j]) continue;
		d[i][j]=std:: min(b[i],a[j]);
	}
	rep(i,1,n) {
		rep(j,1,m) {
			printf("%d ", d[i][j]);
		} puts("");
	}
	return 0;
}

C


首先我们可以算出剩余多少个左括号、右括号。一个比较显然的贪心就是我们先放左括号,再放右括号肯定不会更劣。
然后考虑怎么符合那个任意前缀不合法的情况,可以发现我们只需要保证前缀和>=1就可以了,那么特判一下什么时候放右括号就可以了。

这个挂了是因为没有最后再判一次整个序列是否合法。。

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long long LL;

const int INF=0x3f3f3f3f;
const int N=500005;

char s[N],p[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int main(void) {
	int n; scanf("%d",&n);
	if (n&1) return 0&puts(":(");
	scanf("%s",s+1);
	int l=0,r=0;
	rep(i,1,n) {
		if (s[i]=='(') l++;
		else if (s[i]==')') r++;
	}
	int L=n/2-l,R=n/2-r;
	rep(i,1,n) {
		if (s[i]=='?') {
			if (L) L--,p[i]='(';
			else R--,p[i]=')';
		} else p[i]=s[i];
	}
	l=r=0;
	rep(i,1,n) {
		if (p[i]=='(') l++;
		else r++;
		if (r>l||(r==l&&i<n)) return 0&puts(":(");
	}
	if (l!=r) return 0&puts(":(");
	rep(i,1,n) putchar(p[i]);
	return 0;
}

D


做的时候一直想着怎么变成01问题,然后掉二分答案的坑里面了。。
正解是这样的。我们设f[x]表示x的权值在x为根的叶子里面的排名,通过一些感受可以发现这个排名实际上是固定的。
那么对于max节点就继承排名最小的儿子,min节点就是儿子排名的和了

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long long LL;

const int INF=0x3f3f3f3f;
const int N=500005;

struct edge {int y,next;} e[N];

int f[N],op[N],ls[N],edCnt,cnt;

void add_edge(int x,int y) {
	e[++edCnt]=(edge) {y,ls[x]}; ls[x]=edCnt;
}

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void dfs(int x) {
	int mx=INF,mn=0;
	for (int i=ls[x];i;i=e[i].next) {
		dfs(e[i].y);
		mx=std:: min(mx,f[e[i].y]);
		mn+=f[e[i].y];
	}
	if (!ls[x]) return (void) (cnt+=(f[x]=1));
	(!op[x])?(f[x]=mn):(f[x]=mx);
}

int main(void) {
	int n=read();
	rep(i,1,n) op[i]=read();
	rep(i,2,n) add_edge(read(),i);
	dfs(1);
	printf("%d\n", cnt-f[1]+1);
	return 0;
}

E


首先观察2019和n的范围,这提示我们答案大概是扫一行扫一列然后做一些剩余操作这样子
考虑蛇的端点有什么性质,那就是头和尾所在格子度数恰好为1,其余蛇身所在格子都恰好为2

于是我们可以通过询问n整行来找到头尾所在的行,询问n整列来找到头尾所在的列,当头尾行列均不相同的时候这样是询问恰好2n次的

考虑到有头尾在同一行的情况,这时候我们再扫一次找到头尾所各自在的列,然后在这两列上二分即可。同一列的情况同理。
这样询问次数恰好是2n+2logn的,也就是最坏2020次。怎么办呢?我们只需要扫n-1行就可以确定头尾各自的行,列同理。那么就是最坏2018次了

据说还有随机化询问行和列的骚操作,这个就非常牛逼了。。

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long double ld;
typedef long long LL;

const int INF=0x3f3f3f3f;
const int MOD=1e9+7;
const int ny2=(MOD+1)/2;
const int N=2000005;
const ld pi=acos(-1);
const ld e=exp(1);
const ld eps=1e-8;

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int ask(int x1,int y1,int x2,int y2) {
	printf("? %d %d %d %d\n", x1,y1,x2,y2),fflush(stdout);
	int x; scanf("%d",&x);
	return x;
}

int main(void) {
	int n; scanf("%d",&n);
	int rx1=-1,rx2=-1,ry1=-1,ry2=-1;
	rep(i,1,n-1) if (ask(1,i,n,i)&1) {
		if (ry1==-1) ry1=i;
		else {ry2=i; break;}
	}
	if (ry1>0&&ry2==-1) ry2=n;
	if (ry1==-1) {
		rep(i,1,n-1) if (ask(i,1,i,n)&1) {
			if (rx1==-1) rx1=i;
			else {rx2=i; break;}
		}
		if (rx1>0&&rx2==-1) rx2=n;
		int pos1,pos2;
		for (int l=1,r=n;l<=r;) {
			int mid=(l+r)>>1;
			if (ask(rx1,1,rx1,mid)&1) {
				r=mid-1,ry1=mid;
			} else l=mid+1;
		}
		for (int l=1,r=n;l<=r;) {
			int mid=(l+r)>>1;
			if (ask(rx2,1,rx2,mid)&1) {
				r=mid-1,ry2=mid;
			} else l=mid+1;
		}
	} else {
		int pos1,pos2;
		for (int l=1,r=n;l<=r;) {
			int mid=(l+r)>>1;
			if (ask(1,ry1,mid,ry1)&1) {
				r=mid-1,rx1=mid;
			} else l=mid+1;
		}
		for (int l=1,r=n;l<=r;) {
			int mid=(l+r)>>1;
			if (ask(1,ry2,mid,ry2)&1) {
				r=mid-1,rx2=mid;
			} else l=mid+1;
		}
	}
	printf("! %d %d %d %d\n", rx1,ry1,rx2,ry2),fflush(stdout);
	return 0;
}

F


考虑把长度为l的线段映射到长度为1的线段上,取出2n+1个端点来组成n个相邻区间。答案就是被覆盖至少k次区间的期望数量*每个区间的期望长度

考虑怎么求被覆盖至少k次区间的数量,我们可以把一段区间视为一对左右匹配的左右括号。设f[i,j]表示前i个端点,还有j个端点没有匹配右端点的方案数,转移的话讨论一下i这个位置放左括号还是右括号就可以了。
然后我们枚举一个分界点i,覆盖次数j,合并两端造成的贡献就是f[i,j]*f[n*2,j]*j!,也就是前半段方案*后半段方案,而j对括号可以任意匹配所以还要一个阶乘。

Code


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
#define copy(x,t) memcpy(x,t,sizeof(x))

#define fi first
#define se second

typedef std:: pair <int,int> pair;
typedef long double ld;
typedef long long LL;

const int INF=0x3f3f3f3f;
const int MOD=998244353;
const int ny2=(MOD+1)/2;
const int N=4005;
const ld pi=acos(-1);
const ld e=exp(1);
const ld eps=1e-8;

LL fac[N],f[N][N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):v,ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void upd(LL &x,LL v) {
	x+=v,(x>=MOD)?(x-=MOD):0;
}

LL ksm(LL x,LL dep) {
	LL res=1;
	for (;dep;dep>>=1,x=x*x%MOD) {
		(dep&1)?(res=res*x%MOD):0;
	}
	return res;
}

int main(void) {
	fac[0]=fac[1]=1;
	rep(i,2,N-1) fac[i]=fac[i-1]*i%MOD;
	int n=read(),k=read(),l=read();
	f[1][1]=1;
	rep(i,1,n*2) rep(j,0,std:: min(i,n)) {
		if (!f[i][j]) continue;
		upd(f[i+1][j+1],f[i][j]);
		if (j) upd(f[i+1][j-1],f[i][j]*j%MOD);
	}
	LL ans=0;
	rep(i,1,n*2) rep(j,k,std:: min(i,n)) {
		upd(ans,f[i][j]*f[n*2-i][j]%MOD*fac[j]%MOD);
	}
	ans=ans*ksm(f[n*2][0],MOD-2)%MOD;
	ans=ans*l%MOD*ksm(n*2+1,MOD-2)%MOD;
	printf("%lld\n", ans);
	return 0;
}

你可能感兴趣的:(c++,codeforces,杂文,Codeforces)