CF1480 题解

CF1480A Yet Another String Game

Problem

传送门CF1480A
给出一个字符串,两人轮流操作,每次操作可以将一个字符改为另外一个字符,当不可以不改动,先手目标是让最终字符串字典序最小,后手目标相反,求最终字符串。

Sol

贪心地模拟即可。

#define in read()

int read(){int x = 0,sgn = 1;char ch; for(;!isdigit(ch);ch=getchar())if(ch=='-')sgn=-1;for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);return x*sgn;}
 
int main(){
	int T = in;
	while(T--){
		char s[55];
		scanf("%s",s+1); int n = strlen(s+1);
		for(int i = 1;i <= n;i++){
			if(i & 1) s[i] = s[i] == 'a' ? 'b' : 'a';
			else s[i] = s[i] == 'z' ? 'y' : 'z';
		}
		printf("%s\n",s+1);
	}
	return 0;
}

CF1480B The Great Hero

Problem

传送门CF1480B
有一个英雄,需要击败一堆怪物,他可以每次 1v1 地打怪物,英雄有两个属性,\(A\) (攻击力), \(B\) (血量),怪物也有两个属性 \(a\) , \(b\) (攻击、血量),每一轮战斗后,英雄血量扣除 \(a\) , 怪物血量扣除 \(A\) ,如果血量小于等于 \(0\) , 直接去世,问英雄最后能否杀死所有怪物。

注意:英雄可以选择击杀的顺序,在杀死所有怪物后,英雄可以牺牲(即可以同归于尽)。

Sol

按题意模拟打怪,但是英雄最后可以同归于尽,因此把攻击力最大的怪物弄到最后即可。

#define in read()

const int N = 1e5+10;
int n;
ll A,B;
struct mon{ll a,b;}al[N];
bool operator < (mon x,mon y){return x.a < y.a;}

void solve(){
	for(int i = 1;i <= n;i++){
		if(B <= 0){puts("NO");return;}
		int tim = (al[i].b + A - 1) / A; //因为要杀死,所以向上取整
		B -= tim * al[i].a;
		if(B <= 0){
			if(B + al[i].a > 0) continue;
			puts("NO");return;
		}
	}puts("YES");
}

int main(){
	int t = in;
	while(t--){
		A = in,B = in,n = in;
		for(int i = 1;i <= n;i++) al[i].a = in;
		for(int i = 1;i <= n;i++) al[i].b = in;
		sort(al+1,al+n+1);
		solve();
	}
	return 0;
}

CF1480C Searching Local Minimum

Problem

传送门CF1480C CF1479A
交互题,给你一个长度为 \(n\)\(\leq 10^5\))的序列 \(A\),你每次可以问一个位置的值,找出一个位置 \(i\) , 使得 \(A[i]\) < \(\min{A[i-1],A[i+1]}\) ,(\(A[0],A[n+1] = \inf\)) , 你最多可以问 \(100\) 次。

Sol

先特判两端,然后考虑二分。

如果一个 \(mid\) 不行,一定是这样:

或者:

情况一,\([l,mid-1]\) 一定有答案,情况二,\([mid+1,r]\) 一定有答案。

二分就完了:

#define in read()

const int N = 1e5+10;

int a[N],n;

void Find(int x){printf("! %d\n",x);fflush(stdout);exit(0);}

void query(int x){//记忆化
	if(a[x]) return;
	printf("? %d\n",x);fflush(stdout);
	a[x] = in;
}

bool able(int x){//check
	query(x); query(x-1); query(x+1);
	if(a[x] < min(a[x-1],a[x+1])) return 1;
	return 0;
}

int main(){
	n = in; a[0] = N+1,a[n+1] = N+1;
	int l = 1,r = n;
	if(able(l)) Find(l);
	if(able(r)) Find(r);
	while(l <= r){
		int mid = l+r>>1;
		if(able(mid)) Find(mid);
		int lm = a[mid-1],rm = a[mid+1];
		if(lm < rm) r = mid-1;
		else l = mid+1;
	}
	Find(l);
	return 0;
}

CF1480D1 Painting the Array I

Problem

传送门CF1480D1 CF1479B1

upd : 2.22 修改了英文不好的错误。

对一个序列 \(A\) ($|A| <= 10^5 $)黑白染色,记一个位置上一个与它同色的位置为 \(las\)(没有则为 \(0\)),若 $las = 0 $ or $ a[las] \neq a[i]$ , 你将的到一的贡献,求最大贡献。

Sol

看起来是贪心,然后 WA 了几发,因为想法太 naive 了。

比赛时自造的 \(hack\) 数据

7

2 2 2 1 3 2 2

\(naive\) 的做法:

int main(){
    n = in;for(int i = 1;i <= n;i++) a[i] = in;
	int tot = 0;
	for(int i = 1;i <= n;i++){
		if(en[0] == a[i]){
			if(en[1] != a[i]) en[1] = a[i],tot++;
		}else en[0] = a[i],tot++;
	}printf("%d\n",tot);
	return 0;
}

直接被叉爆。

于是 又交了一个更 naive 的

考虑为什么被叉爆了:

问题就在如果一个位置两个末尾都可以选的情况,这个时候,就尽量帮后面一个位置增加贡献,改一改就行了。

#define in read()

const int N = 1e5+10;
 
int a[N],n,en[3];
 
int main(){
	n = in;for(int i = 1;i <= n;i++) a[i] = in;
	int tot = 1;
	en[1] = a[1];
	for(int i = 2;i <= n;i++){
		if(a[i] != en[1]) {
			tot++;
			if(en[1] != a[i+1] && a[i] != en[2]) en[2] = a[i];
			else en[1] = a[i];
		}
		else if(a[i] != en[2]) {
			tot++; en[2] = a[i];
		}
	}printf("%d\n",tot);
	return 0;
}

CF1480D2 Painting the Array II

Problem

传送门CF1480D2 CF1479B2
对一个序列 \(A\) ($|A| <= 10^5 $)黑白染色,记一个位置上一个与它同色的位置为 \(las\)(没有则为 \(0\)),若 \(las = 0 \or a[las] \neq a[i]\) , 你将的到一的贡献,求最 贡献。

Sol

WA 了 \(\inf\) 发,一直没调出。

因为比赛的时候是 DP 解法,想讲 DP 的吧。

首先要去重,将 $a[i] = a[i-1] $ 的合并成一个数(因为没有贡献),设 \(f[i]\) 代表以 \(i\) 结尾最小贡献。

\(f[i] = \min{(f[p]+i-p-1+a[p-1] != a[i])},p \in [1,i-1]\)

然后 \(p\) 有用的转移点一定不多,\(p = i-1 \space or \space las[a[i]]+1\) , (比赛时想到了,但是搞得有点复杂,不好合并,就一直WAWAWA)

#define in read()

// DP Sol

const int N = 1e5+10;

int n,a[N],f[N],en[N],val[N],las[N],m;

int main(){
	n = in;
	for(int i = 1;i <= n;i++) a[i] = in;
	for(int i = 1;i <= n;i++) if(a[i] != a[m]) a[++m] = a[i];
	int ans = m; f[1] = 1;las[a[1]] = 1;
	for(int i = 2;i <= m;i++){
		f[i] = f[i-1] + (a[i] != a[i-1]);
		if(las[a[i]]){
			int l = las[a[i]]+1;
			f[i] = min(f[i],f[l]+i-l-1);
		}
		las[a[i]] = i;
		ans = min(ans,f[i] + m - i);
	}
	printf("%d\n",ans);
	return 0;
}

当然,贪心的解法更妙。官方题解大概说此题就是 Bélády's algorithm

大概就是记个 \(nxt[i]\) , 表示 \(a[i]\) 下一次出现的地方,类似于上一题,当一个数放在两个序列末尾时,都会有 1 的贡献时,我们优先放那个 \(nxt\) 更远的位置,因为\(nxt\) 更进的位置更加有 "活" 下来的机会使得贡献不增(感性理解吧)。

#define in read()

const int N = 1e5+10;

int n,a[N],en[2],nxt[N],p[N];

int main(){
	n = in;
	for(int i = 1;i <= n;i++) a[i] = in,p[a[i]] = N;
	for(int i = n;i >= 1;i--) nxt[i] = p[a[i]],p[a[i]] = i;
	int ans = 0; nxt[0] = N+1;
	for(int i = 1;i <= n;i++){
		if(a[en[1]] == a[i]) en[1] = i;
		else if(a[en[0]] == a[i]) en[0] = i;
		else{
			if(nxt[en[0]] > nxt[en[1]]) en[0] = i;
			else en[1] = i;
			ans++;
		}
	}
	printf("%d\n",ans);
	return 0;
}

CF1480E Continuous City

Problem

传送门CF1480E CF1479C
对一张 \(n\) 个点的有向图定义\((L,R)\) 连续为 : 从\(1\)\(n\) 的每条路径中,长度为 \(i \in[L,R]\) 的路径只出现一次。

给定 \(L,R\) ,求构造满足的图。

Sol

感觉挺妙的。

我们可以构造一个合法的解通过一下几步。

  1. \(L = 1 ,R = 2 ^ k\)

构造一排城市,对于编号为 \(x,2 \leq x \leq k+2\) 的城市,满足以它为终点的是一个 \((1,2^{x-2})\) 连续图。

假设当前我们已经构造出来了一个 \((1,2^t)\) 连续图,那么我们只要在加入一个新点 \(t+3\) ,并加边 \((1,t+3,1)\)\((x,t+3,2^{x-2})\) ,这样就构造出来一个 \((1,2^{t+1})\) 连续图

2.\(L = 1,R\) 不能表示为 \(2^k\)

那么,将 \(R\) 二进制拆分,设 \(R-1 = \sum_\limits{i=0}^k R_i\times2^i\) ,首先用步骤一构造出一张图,新建一个点 \(x\),加边\((1,x,1)\) ,然后对于每个 \(R_i = 1\) , 加边\((i+2,x,1+\sum_\limits{j=i+1}^k R_i \times 2 ^ i)\) ,自己画个图就知道了。

3.\(L!=1\)

转化为问题\((1,R-L+1)\) , 然后新建点\(x\),加边 \((x-1,x,L-1)\) , 即可。

#define in read()

struct edge{int u,v,w;};
vector<edge> e;
void link(int x,int y,int w){e.pb((edge){x,y,w});}

int solve(int L,int R){
	if(L > 1){
		int x = solve(1,R-L+1);
		link(x,x+1,L-1);
		return x+1;
	}
	if((R & -R) == R){
		int tot = 2;
		for(int i = 1;i <= R;i<<=1,tot++){
			link(1,tot,1);
			for(int j = 2;j < tot;j++) link(j,tot,1<<(j-2));
		}
		return tot - 1;
	}
	int num = 0;
	for(;(1<<(num+1))<=(R-1);num++);
	int x = solve(1,1<<num)+1;;
	link(1,x,1);
	for(int i = 0;i <= num;i++)
		if((R-1) >> i & 1)
			link(i+2,x,1+(R-1>>i+1<<i+1));
	return x;
}

int main(){
	int L = in, R = in;
	int ans = solve(L,R);
	printf("YES\n%d %d\n",ans,e.size());
	for(int i = 0;i < e.size();i++) {
		printf("%d %d %d\n",e[i].u,e[i].v,e[i].w);
	}
	return 0;
}

总结

1.以后要多做构造题。

2.二进制拆分是一种解决构造题的好方法。

3.要多练贪心

posted @ 2021-02-11 16:05  Werner_Yin  阅读(143)  评论(0编辑  收藏  举报