codeforces round #734 (div 3)
d2 - Domino (hard version)
定義多米諾骨牌是一個 \(1\times 2\) 的小矩形 . 給定 \(n,m,k\),求是否可以將一個 \(n\times m\) 的方格( \(n\) 行 \(m\) 列)用多米諾密鋪(不重不漏地鋪滿),其中橫向的多米諾有 \(k\) 個。若可以,則給出任意一種構造方案。
輸出方案時,輸出一個由小寫字母組成的 \(n\times m\) 的字母矩陣,兩個相鄰的單元格擁有相同的字母表明這兩個單元格由一個多米諾覆蓋,否則不算。
\(1\leq t\leq 10\)
\(1\leq n,m\leq 100,\ 0\leq k\leq \frac{nm}{2},\ n\cdot m\)
是偶數
暑假比賽時想了很久都沒有想出來,現在來看,為什麼有點一眼 ...
可以將矩形分類解決,題目中要求要不重不漏地鋪滿 .
-
\(n\) 是奇數,\(m\) 是偶數 .
那麼,第一行都必須要鋪橫向的 . 要鋪 \(\frac{m}{2}\) 個 . 所以,如果 \(k<\frac{m}{2}\) 的可以判斷無解了 . 接下來,因為縱向的要佔據偶數行,所以,橫向的必須兩兩鋪,才能保證最後縱向的能不重不漏地鋪滿 . 如果 \(k-\frac{m}{2}\) 不是偶數的也可以判斷無解了 . 剩下的暴力模擬,先放第一行,再把橫向的放完,剩下放縱向的 .
-
\(n\) 是偶數,\(m\)
第一列必須用縱向的鋪 . 所以,如果 \(k<\frac{n(m-1)}{2}\) 或者 \(k\) 是奇數的是無解的 ,否則有解 . 接著,先放第一列的縱向,再兩個兩個放橫向的 . 剩下放縱向的 .
-
\(n\) 是偶數,\(m\) 是奇數 .
\(k\) 是奇數的是無解的,否則有解 . 先兩個兩個放橫向的,剩下的放縱向的 .
最後用字母表示也不是很難 . 要判斷一下,不能用這兩個格子四周的格子 .
時間複雜度 : \(\mathrm O(tnm)\)
空間複雜度 : \(O(nm)\)
code
#include<bits/stdc++.h> using namespace std; inline int read(){ char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); int res=0; while(ch>='0'&&ch<='9'){ res=(res<<3)+(res<<1)+ch-'0'; ch=getchar(); } return res; } inline void print(int res){ if(res==0){ putchar('0'); return; } int a[10],len=0; while(res>0){ a[len++]=res%10; res/=10; } for(int i=len-1;i>=0;i--) putchar(a[i]+'0'); } const int dx[]={1,0,-1,0}; const int dy[]={0,1,0,-1}; int t; int n,m,k; int cnt=0; int a[110][110]; char ans[110][110]; bool used[30]; void check(int x,int y){ for(int i=0;i<4;i++){ int nx=x+dx[i],ny=y+dy[i]; if(nx<0||nx>n||ny<0||ny>m)continue; if(ans[nx][ny]!=-1)used[ans[nx][ny]-'a']=true; } } void output(){ memset(ans,-1,sizeof(ans)); for(int i=0;i<n;i++)for(int j=0;j<m;j++){ if(ans[i][j]!=-1)continue; if(j+1<m&&a[i][j]==a[i][j+1]){ memset(used,0,sizeof(used)); check(i,j); check(i,j+1); char c; for(int l=0;l<26;l++){ if(!used[l]){ c=l; break; } } ans[i][j]=ans[i][j+1]='a'+c; } if(i+1<n&&a[i][j]==a[i+1][j]){ memset(used,0,sizeof(used)); check(i,j); check(i+1,j); char c; for(int l=0;l<26;l++){ if(!used[l]){ c=l; break; } } ans[i][j]=ans[i+1][j]='a'+c; } } puts("YES"); for(int i=0;i<n;i++){ for(int j=0;j<m;j++)putchar(ans[i][j]); putchar('\n'); } } void solve1(){ if(k<m/2||(k-m/2)%2!=0){ puts("NO"); return; } for(int i=0;i<m/2;i++){ a[0][i<<1]=a[0][i<<1|1]=cnt++; } k-=m/2; for(int i=0;i<m/2;i++){ for(int j=1;j<n;j++){ if(k==0)break; a[j][i<<1]=a[j][i<<1|1]=cnt++; // cout<<j<<","<<(i<<1)<<" "<<j<<","<<(i<<1|1)<<endl; k--; } } for(int i=1;i<n;i+=2)for(int j=0;j<m;j++){ if(a[i][j]==-1&&a[i+1][j]==-1){ a[i][j]=a[i+1][j]=cnt++; } } output(); } void solve2(){ if(k>n*(m-1)/2||k%2!=0){ puts("NO"); return; } for(int i=0;i<n/2;i++){ a[i<<1][0]=a[i<<1|1][0]=cnt++; } for(int i=1;i<m;i+=2){ for(int j=0;j<n;j++){ if(k==0)break; k--; a[j][i]=a[j][i+1]=cnt++; } } for(int i=0;i<n;i+=2){ for(int j=0;j<m;j++){ if(a[i][j]==-1&&a[i+1][j]==-1){ a[i][j]=a[i+1][j]=cnt++; } } } output(); } void solve3(){ if(k%2!=0){ puts("NO"); return; } for(int i=0;i<m;i+=2){ for(int j=0;j<n;j++){ if(k==0)break; k--; a[j][i]=a[j][i+1]=cnt++; } } for(int i=0;i<n;i+=2){ for(int j=0;j<m;j++){ if(a[i][j]==-1&&a[i+1][j]==-1){ a[i][j]=a[i+1][j]=cnt++; } } } output(); } void solve(){ n=read();m=read();k=read(); cnt=0; memset(a,-1,sizeof(a)); if(n&1)solve1(); else if(m&1)solve2(); else solve3(); } int main(){ t=read(); while(t--){ solve(); } return 0; } /*inline? ll or int? size? min max?*/
e - Fixed Points
一個整數序列 \(a1,a2,...,a_n\) ,一次操作,可以刪除一個數,然後該數右側的數向左移動一個單位。對於一個長度為 \(n\) 的整數序列 \(b_i\) ,求最少需要刪除幾個數後,會有至少 \(k\) 個 \(i\) 滿足 \(b_i=i\) 。
\(1\leq t\leq 100\)
\(1\leq k\leq n\leq 2000,\ 1\leq a_i\leq n,\ \sum n\leq 2000\)
看到 \(2\cdot 10^3\) 的範圍,就想到應該是 \(\mathrm O(n^2)\) 的做法 .
考慮這樣一個 \(dp\) ,
\(f(i,j)\) 代表到了第 \(i\) 個數,一共刪除了 \(j\) 個數最多有多少個 \(i\) 滿足 \(b_i=i\) .
轉移方程為 :
-
刪掉當前這個數
\(f(i,j)\rightarrow f(i+1,j+1)\)
-
保留當前這個數
-
如果 \(a_{i+1}=i+1-j\) ,\(f(i,j)+1\rightarrow f(i+1,j)\)
即 \(a_{i+1}\) 對應著 \(b_{i+1-j}\) .
-
否則, \(f(i,j)\rightarrow f(i+1,j)\)
-
最後從小到大列舉 \(f(n-1,i)\) ,找到第一個 \(f(n-1,i)\geq k\) 的 \(i\) 即為答案 .
時間複雜度 : \(\mathrm O(n^2)\)
空間複雜度 : \(\mathrm O(n^2)\)
code
#include<bits/stdc++.h>
using namespace std;
int t;
int n,k;
int a[2010];
int dp[2010][2010];
inline void upd(int &x,int y){
x=max(x,y);
}
void solve(){
cin>>n>>k;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)a[i]--;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
dp[i][j]=0;
}
}
dp[0][0]=(a[0]==0);
dp[0][1]=0;
for(int i=0;i+1<n;i++){
for(int j=0;j<=i+1;j++){
upd(dp[i+1][j],dp[i][j]+(a[i+1]==i+1-j));
upd(dp[i+1][j+1],dp[i][j]);
}
}
int ans=-1;
for(int i=0;i<=n;i++){
if(dp[n-1][i]>=k){
ans=i;
break;
}
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
solve();
}
return 0;
}
/*inline? ll or int? size? min max?*/
f - Equidistant Vertices
給定一棵有 \(n\) 個點的樹,要求選出 \(k\) 個點,使得這 \(k\) 個點兩兩距離相同。答案模 \(10^9+7\) 。
\(1\leq t\leq 10\)
\(2\leq k\leq n\leq 100\)
設選出的 \(k\) 個點為黑點,其他的為白點 .
考慮這 \(k\) 個距離兩兩相同的點的排布,在樹的一條鏈上,必定至多有兩個點為黑點 .
所以,列舉根節點 \(r\) ,設剩下的 \(k\) 個點到這個點距離相同 . 所有的情況都可以歸結到這個樣子 .
觀察可得,這 \(k\) 個節點必定存在與不同的子樹中 . 否則,在同一個子樹的節點之間的距離會比到其他節點的距離要小 .
接著列舉 \(k\) 個點到 \(r\) 的距離 \(d\) ,通過 \(dfs\) 求出每個子樹距離 \(r\) 距離為 \(d\) 的節點個數 .
因為每個節點最多選 \(1\) 個,所以可以用一個揹包求出 .
用 \(f(i,j)\) 表示到了第 \(i\) 個子樹,選了 \(j\) 個黑點的方案數 .
用 \(cnt(i)\) 表示第 \(i\) 個子樹中距離 \(r\) 距離為 \(d\) 的節點個數 .
\(f(i,j)=f(i-1,j-1)\times cnt(i)+f(i-1,j)\)
列舉根 \(\mathrm O(n)\),列舉距離 \(\mathrm O(n)\) ,\(dfs\) \(\mathrm O(n)\) 之後揹包 \(\mathrm O(n^2)\) .
時間複雜度 : \(\mathrm O(n^4)\)
空間複雜度 : \(\mathrm O(n^2)\)
其實只跑了 31ms ,因為仔細想想,沒有什麼資料能使這個跑滿,一般都遠遠跑不滿 .
code
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int t;
int n,k;
vector<int>g[110];
int dep[110],dp[110][110];
inline void upd(int &x,int y){
x=(x+y)%mod;
}
void dfs(int x,int fa,int d,int &cnt){
if(dep[x]==d)cnt++;
for(int i=0;i<(int)g[x].size();i++){
int to=g[x][i];
if(to==fa)continue;
dep[to]=dep[x]+1;
dfs(to,x,d,cnt);
}
}
void solve(){
cin>>n>>k;
for(int i=0;i<n;i++)g[i].clear();
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
u--;v--;
g[u].push_back(v);
g[v].push_back(u);
}
if(k==2){
cout<<1ll*n*(n-1)/2%mod<<endl;
return;
}
int ans=0;
for(int r=0;r<n;r++)for(int d=1;d<=n;d++){
if((int)g[r].size()<k)continue;
dp[0][0]=1;
for(int i=0;i<(int)g[r].size();i++){
int cnt=0;
dep[g[r][i]]=1;
dfs(g[r][i],r,d,cnt);
for(int j=0;j<=i+1;j++)dp[i+1][j]=0;
for(int j=0;j<=i;j++){
if(cnt>0)upd(dp[i+1][j+1],1ll*dp[i][j]*cnt%mod);
upd(dp[i+1][j],dp[i][j]);
}
}
cout<<endl;
upd(ans,dp[(int)g[r].size()][k]);
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
solve();
}
return 0;
}
/*inline? ll or int? size? min max?*/