Educational Codeforces Round 100 (Rated for Div. 2) CF1463A-F 题解

A.
如果以 7 7 7为一个周期,可以知道我们可以打 9 9 9点伤害。所以要想完美过关,怪的生命值和一定是 9 9 9的倍数。同时我们可以算出需要 s u m / 9 sum/9 sum/9个周期才能解决,因此每一个怪的生命值都要大于等于 s u m / 9 sum/9 sum/9

代码:

#include <iostream>
#include <cstdio>

using namespace std;

int a,b,c,T;

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d%d",&a,&b,&c);
		int sum=a+b+c;
		if ((sum%9==0) && (a>=sum/9) && (b>=sum/9) && (c>=sum/9)) printf("YES\n");
		                                                     else printf("NO\n");
	}
}

B.
我们可以这样构造数列,
1 , a 2 , 1 , a 4 , . . . . 1,a_2,1,a_4,.... 1,a2,1,a4,....
或者
a 1 , 1 , a 3 , 1 , . . . . a_1,1,a_3,1,.... a1,1,a3,1,....
显然这样的权值就是 a 1 − 1 + a 3 − 1 + . . . a_1-1+a_3-1+... a11+a31+...,或者 a 2 − 1 + a 4 − 1 + . . . a_2-1+a_4-1+... a21+a41+...
显然有一个权值小于 S S S的一半。

代码:

#include <iostream>
#include <cstdio>
#define LL long long

const int maxn=107;

using namespace std;

int T,n;
LL sum,l,r;
LL a[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		sum=l=r=0;
		for (int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			sum+=a[i];
		}
		for (int i=1;i<=n;i++)
		{
			if (i&1) l+=a[i];
			    else r+=a[i];
		}
		if (l<=sum/2)
		{
			for (int i=1;i<=n;i++)
			{
				if (i&1) printf("1 ");
				    else printf("%lld ",a[i]);
			}
		}
		else
		{
			for (int i=1;i<=n;i++)
			{
				if (i&1) printf("%lld ",a[i]);
				    else printf("1 ");
			}
		}
		printf("\n");
	}
}

C.
我们可以记录当前的位置 o p op op以及目标位置 e d ed ed
如果在时刻 T i T_i Ti o p = e d op=ed op=ed,那么就会执行命令 i i i
并处理出时刻 T i + 1 T_{i+1} Ti+1所在位置 d d d。如果 x i x_i xi位于 o p op op d d d之间,这说明这个命令有效。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=1e5+7;

using namespace std;

int T,n;
LL op,ed,ans;
LL a[maxn],b[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]);
		op=0,ed=0,ans=0;
		a[n+1]=2e10+1;
		for (int i=1;i<=n;i++)
		{
			if (op==ed) ed=b[i];
			LL l=op,r;
			if (ed<op)
			{
				if (op-ed<=a[i+1]-a[i]) op=ed;
				                   else op-=a[i+1]-a[i]; 
			}
			else
			{
				if (ed-op<=a[i+1]-a[i]) op=ed;
				                   else op+=a[i+1]-a[i]; 
			}
			r=op;
			if (l>r) swap(l,r);
			if ((l<=b[i]) && (b[i]<=r)) ans++;
		}
		printf("%lld\n",ans);
	}
}

D.
考虑最少需要选多少个最小值与最大值。
以最小值为例,如果某一个数 x x x c n t cnt cnt为小于 x x x且没有出现在序列中并且没有匹配的数的个数。
显然如果 a a a在序列中且 c n t cnt cnt不为 0 0 0,则 c n t − 1 cnt-1 cnt1
如果 a a a在序列中且 c n t cnt cnt 0 0 0,此时必须取一个最小值,另一个数可以选择最大的没有出现的数(如果被选了就次大…);
如果 a a a不存在则 c n t + 1 cnt+1 cnt+1
那么只要满足最少的最小值与最大值个数的方案都是合法的。

代码:

#include <iostream>
#include <cstdio>

const int maxn=2e5+7;

using namespace std;

int T,n,sum,l,r;
int a[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) scanf("%d",&a[i]);
		a[n+1]=2*n+1;
		l=0,r=0;
		sum=0;
		
		for (int i=1;i<=n;i++)
		{
			sum+=a[i]-a[i-1]-1;
			if (!sum) l++;
			     else sum--;
		}
		sum=0;
		for (int i=n;i>0;i--)
		{
			sum+=a[i+1]-a[i]-1;
			if (!sum) r++;
			     else sum--;
		}
		printf("%d\n",n-r-l+1);
	}
}

E.
首先我们考虑第二类限制,显然只有当限制是若干条链的情况才是合法的。
我们可以把每条链缩成一个点,可以用并查集维护,集合的根设为链的开头。
对于第一类限制,我们设 ( x , y ) (x,y) (x,y)表示 x x x y y y的前置条件。
则如果 x x x y y y属于不同的链,那么我们从 y y y的链头向 x x x的链头连边。表示 x x x的链是 y y y的链的前置条件,因为每条链必须一次连续做完。
而如果 x x x y y y处于同一条链,则判断 x x x在链上的顺序是否在 y y y前面即可,不需要连边。
然后对新建的图跑top序,如果存在则有解。

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>

const int maxn=3e5+7;

using namespace std;

int n,m,x,y,cnt;
int b[maxn],r[maxn],last[maxn],to[maxn],dep[maxn],v[maxn],p[maxn],ans[maxn];

vector <int> a[maxn];
queue <int> q;

int find(int x)
{
	if (!p[x]) return x;
	return p[x]=find(p[x]);
}

void uni(int x,int y)
{
	int u=find(x),v=find(y);
	if (u!=v) p[u]=v;
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		b[i]=x;
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		if ((!last[y]) && (find(x)!=find(y)))
		{
			last[y]=x;
			to[x]=y;
			uni(y,x);
		}
		else
		{
			printf("0\n");
			return 0;
		}
	}
	for (int i=1;i<=n;i++)
	{
		if (!last[i])
		{
			int x=i;
			while (x)
			{
				dep[x]=dep[last[x]]+1;
				x=to[x];
			}
		}
	}
	for (int i=1;i<=n;i++)
	{
		int y=b[i];
		if (!y) continue;
		if (find(i)!=find(y))
		{
			a[find(y)].push_back(find(i));
			r[find(i)]++;
		}
		else
		{
			if (dep[y]>dep[i])
			{
				printf("0\n");
 				return 0;
			}
		}
	}
	for (int i=1;i<=n;i++)
	{
		if ((!p[i]) && (!r[i]))
		{
			q.push(i);
			v[i]=1;
			ans[++cnt]=i;
		}
	}
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=0;i<a[x].size();i++)
		{
			int y=a[x][i];
			r[y]--;
			if ((!r[y]) && (!v[y]))
			{
				v[y]=1;
				q.push(y);
				ans[++cnt]=y;
			}
		}
	}
	for (int i=1;i<=n;i++)
	{
		if ((!p[i]) && (!v[i]))
		{
			printf("0\n");
			return 0;
		}
	}
	for (int i=1;i<=cnt;i++)
	{
		int x=ans[i];
		while (x)
		{
			printf("%d ",x);
			x=to[x];
		}
	}
 } 

F.
我们可以从 [ 1 , x + y ] [1,x+y] [1,x+y]选择一些数满足条件。
对于每一个选择的数 i i i,我们选上所有 i ≡ j ( m o d   ( x + y ) ) ( j < = n ) i≡j(mod\ (x+y))(j<=n) ij(mod (x+y))(j<=n),这样构造出的集合是合法的。
证明:显然在同一个区间内选的数一定是合法的,而如果跨区间。
如果不合法则满足 ∣ p + ( x + y ) − q ∣ = x |p+(x+y)-q|=x p+(x+y)q=x或者 ∣ p + ( x + y ) − q ∣ = y |p+(x+y)-q|=y p+(x+y)q=y
化简得到 ∣ p − q ∣ = y |p-q|=y pq=y,或者 ∣ p − q ∣ = x |p-q|=x pq=x,与我们假设的区间内合法矛盾。

至于为什么这样构造出的集合 s i z e size size可以是最大的,可以参考cf的题解。

那么我们只需要考虑 [ 1 , x + y ] [1,x+y] [1,x+y]的每个数选还是不选即可,设 f [ i ] [ s t a ] f[i][sta] f[i][sta]表示前 i i i个数,选的数状态为 s t a sta sta的集合大小最大值。
维护状态 s t a sta sta可以知道 i i i能不能选,如果选了 i i i,集合增加的大小就是 i ≡ j ( m o d   ( x + y ) ) ( j < = n ) i≡j(mod\ (x+y))(j<=n) ij(mod (x+y))(j<=n)
我们可以令 x > y x>y x>y,那么 [ x + 1 , y ] [x+1,y] [x+1,y]的状态我们不需要记录,因为他们不会影响后面的转移。
所以复杂度就是 O ( ( x + y ) ∗ 2 m a x ( x , y ) ) O((x+y)*2^{max(x,y)}) O((x+y)2max(x,y))

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
 
const int maxn=4200007;
 
using namespace std;
 
int n,x,y,p,l,r,ans;
int f[2][maxn],bit[25];
 
int main()
{
	scanf("%d%d%d",&n,&x,&y);
	if (x<y) swap(x,y);
	p=x+y;
	l=n/(x+y);
	r=n%(x+y);
	bit[0]=1;
	for (int i=1;i<=x;i++) bit[i]=bit[i-1]*2;
	int sta=0;
	for (int j=0;j<bit[x];j++) f[sta][j]=-0x3f3f3f3f;
	f[sta][0]=0;
	for (int i=1;i<=x+y;i++)
	{
		
		sta=1-sta;
		for (int j=0;j<bit[x];j++) f[sta][j]=f[1-sta][j];
		for (int j=0;j<bit[x];j++)
		{
			if ((i-y>=1) && (j&bit[i-y-1])) continue;
			if ((i-x>=1) && (j&bit[i-x-1])) continue;
			if (i<=x) f[sta][j|bit[i-1]]=max(f[sta][j|bit[i-1]],f[1-sta][j]+l+(i<=r));
			     else f[sta][j]=max(f[sta][j],f[1-sta][j]+l+(i<=r));
		}
	}
	ans=0;
	for (int j=0;j<bit[x];j++) ans=max(ans,f[sta][j]);
	printf("%d\n",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值