前言
報了名沒打的一場 Div. 4,我是怎麼想到回去做的呢?上課的時候無聊於是隨機了一道 1700 的題,找到了本場比賽的 F 題,我那時還沒發現。過了差不多 \(2\sim3\) 天去隨機了一道 1900,又找到了 G 題,一看 G 題竟然只有 1900,意識到這是 Div. 4,就想著 AK 一場 Div. 4,結果發現 F 做過了,這場我還報名了?於是倒序開題並 AK 了。根據我的習慣,每次完成一套題,就要發一個 Overall Tutorial,於是有了本文。
\(\textsf{Overall}\)
CF1950A
難度:800
直接比較三數即可。
CF1950B
難度:800
把每 \(4\) 格看做一格,當 \(i+j\) 是 \(2\) 的倍數的時候這個格子就是 #
。
CF1950C
難度:800
除了 \(0\) 點鐘特判,其他輸出減一取模加一即可。
CF1950D
難度:1100
先簡化題意:一個數 \(x\) 能否拆成若干只由 0
和 1
組成的數的乘積。注意到這種數不多,列舉出來,計算哪些 \(x\) 可以表示,\(O(1)\) 判斷答案。
CF1950E
難度:1500
顯然 \(x\) 是 \(n\) 的因數,所以可以列舉 \(n\) 的因數,再每 \(x\) 個字元檢查一次。把串 \(S\) 分成 \(x\) 個串,也就是 \(S_1S_2\cdots S_x,S_{x+1}S_{x+2}\cdots S_{2x},\cdots\),把這些子串放進 set 裡去重。如果 set 內只有一個串或兩個滿足條件的串且它們其中一種不超過一個,那麼 \(x\) 就是符合條件的答案。
#include <bits/stdc++.h>
using namespace std;
set<string> st;
map<string,int> mp;
int check(string s,string t)
{
int cnt=0;
for(int i=0;i<s.size();i++) cnt+=(s[i]!=t[i]);
if(cnt>1) return 0;
return 1;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
string s;
cin>>s;
s=' '+s;
for(int i=1;i<=n;i++)
{
if(n%i) continue;
st.clear();
mp.clear();
for(int j=1;j<=n;j+=i)
{
string t=s.substr(j,i);
st.insert(t);
mp[t]++;
}
if(st.size()>2)
{
continue;
}
if(st.size()==1)
{
cout<<i<<'\n';
break;
}
if((mp[*st.begin()]>1&&mp[*(++st.begin())]>1)||!check(*st.begin(),*(++st.begin())))
{
continue;
}
cout<<i<<'\n';
break;
}
}
}
CF1950F
難度:1700
點數不變,那麼平均每層節點越多,深度越小。不難聯想到對於某一層來說,它的節點最大值與它上一層節點個數和剩餘節點數量有關。因此應儘可能地把子節點最多的那 \(a\) 個放到深度小的位置,把次多的 \(b\) 個放到僅比那 \(a\) 個稍深一點的位置,\(c\) 個葉子有多深放多深。
#include <bits/stdc++.h>
using namespace std;
vector<int> G[300010];
int dep[300010];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin>>t;
while(t--)
{
int a,b,c,now=1;
cin>>a>>b>>c;
for(int i=1;i<=a+b+c;i++) G[i].clear(),dep[i]=0;
queue<int> q;
q.push(1);
while(q.size())
{
int u=q.front();
q.pop();
if(a)
{
a--;
G[u].push_back(++now);
q.push(now);
dep[now]=dep[u]+1;
G[u].push_back(++now);
q.push(now);
dep[now]=dep[u]+1;
}
else if(b)
{
b--;
G[u].push_back(++now);
q.push(now);
dep[now]=dep[u]+1;
}
else c--;
}
if(a||b||c)
{
cout<<-1<<'\n';
continue;
}
int ans=0;
for(int i=1;i<=now;i++) ans=max(ans,dep[i]);
cout<<ans<<'\n';
}
}
CF1950G
觀察到 \(n\) 的範圍極小,本題應該是要 \(O(2^n\times n)\) 左右的複雜度,不難想到狀壓 dp。定義 \(dp_{i,j}=0/1\) 表示達到 \(i\) 這個狀態(一個二進位制數,第 \(i\) 首歌選那麼狀態的第 \(i\) 位為 \(1\),否則為 \(0\),\(0\le i<n\))並以第 \(j\) 首歌結尾是否可能。有了狀態,自然推出轉移即某一個狀態 \(i\) 中選擇一個未被選擇的歌並且能與第 \(j\) 首歌接上,那麼假設該歌曲編號為 \(k\),那麼 \(dp_{i+2^k,k}\leftarrow 1\)。初始值的設定也易得:對於每個 \(0\le x<n\),\(dp_{2^x,x}\leftarrow 1\)。答案自然就是滿足 \(dp_{i,j}=1\) 的 \(n-\operatorname{popcount}(i)\) 的最小值。
這題有點卡常,需要注意一些事情,否則就會如圖。
要注意的幾點:
-
popcount 提前處理好,最好是在多測開始前就把 \(0\sim 2^{16}\) 的每個數的 popcount 處理好。
-
關閉同步流或使用 scanf 讀入或開快讀。
-
歌曲流派與作者先離散化,否則常數很大。
-
記錄一個 \(vis_{i,j}\) 表示是否計算過 \(dp_{i,j}\),如果搜尋到狀態 \(i,j\) 時 \(vis_{i,j}=1\) 就直接退出本次搜尋。
-
多測清空。
#include <bits/stdc++.h>
using namespace std;
string g[20],w[20];
int g2[20],w2[20],cl[1<<17];
int dp[1<<17][20],n,cnt=0,vis[1<<17][20];
map<string,int> mp;
void dfs(int sta,int lst)
{
if(vis[sta][lst]) return;
vis[sta][lst]=1;
dp[sta][lst]=1;
for(int i=0;i<n;i++)
{
if((sta>>i&1)||(g2[i]!=g2[lst]&&w2[i]!=w2[lst]))
{
vis[sta+(1<<i)][i]=1;
continue;
}
dfs(sta+(1<<i),i);
}
}
int calc(int k)
{
int ret=0;
for(int i=0;i<16;i++) ret+=k>>i&1;
return ret;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin>>t;
for(int i=0;i<(1<<16);i++) cl[i]=calc(i);
while(t--)
{
mp.clear();
int ans=0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>g[i]>>w[i];
if(!mp[g[i]]) mp[g[i]]=++cnt;
if(!mp[w[i]]) mp[w[i]]=++cnt;
g2[i]=mp[g[i]];
w2[i]=mp[w[i]];
}
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
dp[i][j]=vis[i][j]=0;
for(int i=0;i<n;i++) dfs(1<<i,i);
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
ans=max(ans,dp[i][j]*cl[i]);
cout<<n-ans<<'\n';
}
}