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