Codeforces Round 903 (Div. 3)

[比赛链接]

A. Don't Try to Count

直接用string的可加性,每次 s+=s 相当于翻倍了,因为 \(nm<=25\) 所以最多翻倍5次。

判断什么的直接模拟就行。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
int n,m;
string s,x;
bool check(string s,string t){
	for(int i=0;i<s.length();i++){
		for(int j=0;j<t.length();j++){
			if(i+j>=s.length()) break;
			if(s[i+j]!=t[j]) break;
			if(j==t.length()-1) return 1;
		}
	}
	return 0;
}
int main()
{
    ios::sync_with_stdio(false);
    int t;
	cin>>t;
	while(t--){
		cin>>n>>m>>x>>s;
		for(int i=0;i<=5;i++){
			if(check(x,s)){
				cout<<i<<endl;
				break;
			}
			x+=x;
			if(i==5) cout<<-1<<endl;
		}
	}
    return 0;
}

B. Three Threadlets

根据切割的次数分类讨论,可以求出每种情况 若成功则最后每段长度是多少,判断即可。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
long long a,b,c;
bool check(long long a,long long b,long long c,long long x){
	if(a%x+b%x+c%x!=0) return 0;
	if(a<x||b<x||c<x) return 0;
	if(a/x+b/x+c/x<=6) return 1;
	return 0;
}
int main()
{
    ios::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--){
		cin>>a>>b>>c;
		long long x=a+b+c;
		if(check(a,b,c,x/3)||check(a,b,c,x/4)||check(a,b,c,x/5)||check(a,b,c,x/6)) cout<<"YES\n";
		else cout<<"NO\n";
	}

    return 0;
}

C. Perfect Square

旋转类型的题在草稿纸上模拟一下就知道规律了。

这个题就变成了把正方形矩阵划分成四个区域,让四个区域的某个特定位置都变成这四个特定位置的最大值(直接看代码方便些)。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
const int maxn=1005;
int T,n;
string s[maxn];

int main()
{
    ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>s[i];
			s[i]=' '+s[i];
		}
		int ans=0;
		for(int i=1;i<=n/2;i++){
			for(int j=1;j<=n/2;j++){
				ans+=max(max(max(s[i][j],s[n-j+1][i]),s[n-i+1][n-j+1]),s[j][n-i+1])*4-s[i][j]-s[n-j+1][i]-s[n-i+1][n-j+1]-s[j][n-i+1];
			}
		}
		cout<<ans<<endl;
	}
    return 0;
}

D. Divide and Equalize

可以发现规律 $(a_i \div x)\times (a_j \times x) = a_i \times a_j $ 即经过这些变换之后乘积恒定不变。

所以可以将每个数质因数分解,并统计每个质因数个数,最后只需要判断所有的质因数个数是否都是 \(n\) 的倍数(即能否平均分配到这 \(n\) 个数上)。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
const int maxn=1000005;
int vis[maxn],prime[maxn],cnt,num[500],T,n,a[maxn];
map<int,int> ma;
void work(int x){
	for(int i=1;i<=cnt;i++){
		while(x%prime[i]==0){
			x/=prime[i];
			ma[i]++;
		}
		if(x==1) break;
	}
	if(x>1) ma[x]++;
}
int main()
{
    ios::sync_with_stdio(false);
    for(int i=2;i<=2000;i++){
    	if(!vis[i]) prime[++cnt]=i;
    	for(int j=1;j<=cnt&&i*prime[j]<=2000;j++){
    		vis[i*prime[j]]=1;
    		if(i%prime[j]==0) break;
		}
	}
	cin>>T;
	while(T--){
		ma.clear();
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<=n;i++) work(a[i]);
		int ans=1;
		for(auto i : ma){
			if(i.second%n!=0){
				cout<<"NO\n";
				ans=0;
				break;
			}
		}
		if(ans) cout<<"YES\n";
	}
    return 0;
}

E. Block Sequence

我们发现从一个位置 \(i\) 向后可以直接跳到 \(i+a_i\) 而与 \([i+1,i+a_i]\) 区间里的数没有关系。

像极了dp中的状态转移。

需要从后往前写dp,因为 \(a_i\) 影响的是 \(i\) 后面的一段序列。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
const int maxn=200005;
int n,a[maxn],dp[maxn];
int main()
{
    ios::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		dp[n]=1;dp[n+1]=0;
		for(int i=n-1;i>=1;i--){
			if(i+a[i]<=n) dp[i]=min(dp[i+a[i]+1],dp[i+1]+1);
			else dp[i]=dp[i+1]+1;
		}
		cout<<dp[1]<<endl;
	}

    return 0;
}

F. Minimum Maximum Distance

类似于换根dp。

一开始我们设 \(f[u]\)\(u\)\(u\) 为根的子树中标记点的最远距离。

那么 $f[u]=max{f[v]+1} $,即儿子节点的答案加上儿子节点到父亲的距离(1)。

然后需要进行换根操作(代码中直接延续了 \(f\) 数组,但是其意义变为在整张图的范围内 \(u\) 节点的答案),我们发现父亲节点的信息传递到某个儿子时,我们不知道父亲节点的答案是不是从这个儿子那里得到的。

所以说我们用 \(f[u][0/1]\) 分别记录最远距离、次远距离。

\(f[u][0]==f[v][0]+1\) 那么就用 \(f[u][1]+1\) 来传递给儿子信息,从而实现换根操作。

最后答案就是 \(min\{f[u][0]\}\)

赛场上能写出来我还是很满意的(

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
const int maxn=200005;
struct node{
	int v,next;
}e[maxn*2];
int cnt,p[maxn],a[maxn],ans,fa[maxn],f[maxn][2],son[maxn],vis[maxn],n,m;
void insert(int u,int v){
	cnt++;
	e[cnt].v=v;
	e[cnt].next=p[u];
	p[u]=cnt;
}
void dfs1(int u,int f){
	son[u]=0;
	fa[u]=f;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(v==f) continue;
		son[u]++;
		dfs1(v,u);
	}
}
void dfs2(int u){
	f[u][0]=f[u][1]=-1;
	if(vis[u]) f[u][0]=f[u][1]=0;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(v==fa[u]) continue;
		dfs2(v);
		if(f[v][0]==-1) continue;
		if(f[v][0]+1>f[u][0]) f[u][1]=f[u][0],f[u][0]=f[v][0]+1;
		else if(f[v][0]+1>f[u][1]) f[u][1]=f[v][0]+1;
	}
}
void gengxin(int u,int x){
	if(x==0) return;
	if(x>f[u][0]) f[u][1]=f[u][0],f[u][0]=x;
	else if(x>f[u][1]) f[u][1]=x;
}
void getans(int u){
	if(fa[u]!=-1){
		if(f[fa[u]][0]==f[u][0]+1) gengxin(u,f[fa[u]][1]+1);
		else gengxin(u,f[fa[u]][0]+1);
	}
	ans=min(ans,f[u][0]);
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(v==fa[u]) continue;
		getans(v);
	}
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
	cin>>T;
	while(T--){
		cnt=0;
		cin>>n>>m;
		for(int i=1;i<=n;i++) vis[i]=0,p[i]=-1;
		for(int i=1;i<=m;i++){
			int x;
			cin>>x;
			vis[x]=1;
		}
		for(int i=1;i<n;i++){
			int u,v;
			cin>>u>>v;
			insert(u,v);
			insert(v,u);
		}
		dfs1(1,-1);
		dfs2(1);
		ans=10000000;
		getans(1);
		cout<<ans<<endl;
	}
    return 0;
}

好,我是小丑。

看了一下题解,只需要找到距离最远的两个被标记的点,设距离为 \(d\),答案就是 \(\lceil \frac{2}{d} \rceil\)

方法与找树的直径类似,从任意一个被标记的点出发跑一遍bfs找到距离最远的标记点 \(i\),然后从 \(i\) 出发再跑一遍找到距离最远的标记点 \(j\)\(ij\) 即为所求。

代码不放了。

G. Anya and the Mysterious String

好题。(赛后补的)

首先,因为题目问的是区间内有无回文串,所以我们只需要找到长度最短的回文串就行。

要么是形如 \(AA\) ,要么是形如 \(ABA\)

我们把前者和后者形式的回文串的首位置分别放在两个set中。

假如没有修改操作,那么每次询问的时候只需要分别在set里 lowerbound 一下第一个大于等于 l 的位置,看一看该回文串右端是否大于r即可。

加上区间加的修改操作怎么办呢?

我们发现如果回文串在这个区间内,那么是不影响答案的。

所以每次修改只会影响两端的位置的几个位置的回文情况。

区间修改完后单点查询两端位置的字符是什么,判断,分别更新两个set即可。

树状数组(或者线段树)都可以维护。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
const int maxn=200005;
int n,m,a[maxn],d[maxn]; 
set<int> a1,a2;
inline int lowbit(int x){
	return x&(-x);
}
void update(int x,int v){
	if(v<0) v+=(-v)/26*26;
	v=(v+26)%26;
	for(int i=x;i<=n;i+=lowbit(i)) d[i]+=v,d[i]%=26; 
}
int query(int x){
	int res=0;
	for(int i=x;i>0;i-=lowbit(i)) res+=d[i];
	return res%26;
}
void check(int x){
	if(x<=0) return;
	if(x<n){
		if(query(x)==query(x+1)) a1.insert(x);
		if(query(x)!=query(x+1)) a1.erase(x);
	}
	if(x<n-1){
		if(query(x)==query(x+2)) a2.insert(x);
		if(query(x)!=query(x+2)) a2.erase(x);
	}
}
int main()
{
    ios::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--){
		a1.clear();a2.clear();
		cin>>n>>m;
		char c;
		for(int i=0;i<=n+1;i++) d[i]=0;
		for(int i=1;i<=n;i++) cin>>c,a[i]=c-'a';
		for(int i=1;i<=n;i++) update(i,a[i]-a[i-1]);
		for(int i=1;i<n;i++) check(i);
		while(m--){
			int tp;
			cin>>tp;
			if(tp==1){
				int l,r,x;
				cin>>l>>r>>x;
				update(l,x);
				update(r+1,-x);
				check(l-2);
				check(l-1);
				check(r-1);
				check(r);
			}else{
				int l,r;
				cin>>l>>r;
				auto r1=a1.lower_bound(l);
				auto r2=a2.lower_bound(l);
				if(r1!=a1.end()&&(*r1)<=r-1){
					cout<<"NO\n";
					continue;
				}
				if(r2!=a2.end()&&(*r2)<=r-2){
					cout<<"NO\n";
					continue;
				}
				cout<<"YES\n";
			}
		}
	}
    return 0;
}

比赛总结

个人感觉比较满意,差一题AK。

不足之处就是F题没有找到最简单的方法,G题对于很多细节的处理也不恰当。

现在看来基本做题思路恢复差不多了,以后需要多做做难题了,不能一直在div3和ABC划水了。

posted @ 2023-10-15 14:25  尹昱钦  阅读(225)  评论(1编辑  收藏  举报