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划水了。