CF習題集三
一、CF8C Looking for Order
題目描述
\(Lena\)喜歡秩序井然的生活。一天,她要去上大學了。突然,她發現整個房間亂糟糟的——她的手提包裡的物品都散落在了地上。她想把所有的物品都放回她的手提包。但是,這裡有一點問題:她一次最多隻能拿兩個物品,她也不能移動她的手提包。並且,因為她愛整潔的習慣,如果她拿起了一個物品,她也不能將它放在其他地方,除非放回她的手提包。
\(Lena\)把她的房間劃分為了一個平面直角座標系。現在Lena給你她的手提包和每個散落的物品的座標(當然,一開始的時候她就和手提包站在一個地方)。她從座標 \((x1,y1)\)走到座標 \((x2,y2)\) 需要用 \((x1-x2)^2+(y1-y2)^2\) 單位的時間。現在,\(Lena\)將告訴你她的房間的情況,請你為\(Lena\)找到一個拾起每個物品的順序,使她拾起所有物品所需的總時間最小。當然,\(Lena\)最後需要返回她的手提包。
分析
\(n\)的範圍比較小,因此我們考慮狀壓\(DP\)
我們設\(f[i]\)為當前拾取的物品狀態為\(i\)時的最小花費
那麼我們就可以寫出如下的狀態轉移方程
f[i|(1<<(j-1))|(1<<(k-1))]=f[i]+dis[j][0]+dis[j][k]+dis[k][0];
如果單純地這樣寫而不加任何優化是會\(T\)掉的
實際上,對於兩個物品,我們先拿走哪一個或者後拿走哪一個對結果沒有影響
因此,我們可以人為地規定一個拿取的順序
即對於一個\(j\)一旦它匹配成功,我們就跳出迴圈,不再匹配
而去匹配下一個\(j\)
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=25;
int f[1<<maxn],jl[1<<maxn];
int jlx[maxn],jly[maxn],dis[maxn][maxn];
int ans[1<<maxn],cnt;
int main(){
memset(f,0x3f,sizeof(f));
scanf("%d%d",&jlx[0],&jly[0]);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&jlx[i],&jly[i]);
}
for(int i=0;i<=n;i++){
for(int j=i+1;j<=n;j++){
dis[i][j]=dis[j][i]=(jlx[i]-jlx[j])*(jlx[i]-jlx[j])+(jly[i]-jly[j])*(jly[i]-jly[j]);
}
}
f[0]=0;
int mmax=(1<<n)-1;
for(int i=0;i<=mmax;i++){
for(int j=1;j<=n;j++){
if(i&(1<<(j-1))) continue;
for(int k=1;k<=n;k++){
if(i&(1<<(k-1))) continue;
int now=i|(1<<(j-1))|(1<<(k-1));
if(f[now]<=f[i]+dis[j][0]+dis[j][k]+dis[k][0]) continue;
f[now]=f[i]+dis[j][0]+dis[j][k]+dis[k][0];
jl[now]=i;
}
break;
}
}
printf("%d\n",f[mmax]);
while(mmax){
printf("0 ");
for(int i=1;i<=n;i++){
if((mmax^jl[mmax])&(1<<((i-1)))) printf("%d ",i);
}
mmax=jl[mmax];
}
printf("0 ");
return 0;
}
二、CF510D Fox And Jumping
題目描述
給出 \(n\) 張卡片,分別有 \(l_i\) 和 \(c_i\)。在一條無限長的紙帶上,你可以選擇花 \(c_i\) 的錢來購買卡片 \(i\),從此以後可以向左或向右跳 \(l_i\) 個單位。問你至少花多少元錢才能夠跳到紙帶上全部位置。若不行,輸出 \(-1\)。
分析
我們先只考慮購買了兩個卡片的情況
我們設這兩個卡片跳躍的距離分別為\(a,b\)
其中第一張卡片使用了\(x\)次,第二張卡片使用了\(y\)次
那麼跳躍的距離\(l=ax+by\)
要使方程有解,則必有\(l mod \gcd(a,b)=0\)
要使\(l\)取到任意整數,則\(\gcd(a,b)=1\)
因此,原題就變成了在\(n\)個數中選取幾個數,使得這些數的最大公因數為\(1\)
求所有方案中花費最小的方案
一種可行的做法是設\(f[i]\)表示選擇一些數並且最大公約數為\(i\)時的最小花費
利用\(map\)進行轉移
但是現在我們考慮怎麼用暴搜過掉這個題
最初始的暴搜應該比較好定義
我們傳四個引數,分別是當前已經選到了第幾個數、當前已經選了幾個數、當前選擇的所有數的價值之和、當前所有數的最大公因數
這樣的暴搜不加任何剪枝會\(T\)到飛起
因此我們考慮怎麼去優化
剪枝一、如果當前價值已經大於所選價值,那麼就沒有必要繼續向下選
剪枝二、如果當前選擇的數量大於9,那麼就沒有必要繼續向下選
因為如果有\(10\)個不同的質因子,此時數會很大,已經超過了\(l[i]\)的最大值
剪枝三、提前將-1的情況預處理出來
剪枝四、如果當前列舉的\(l[i]\)是之前列舉的公因數的倍數,那麼當前的\(l[i]\)一定不會使之後的公因數變小,選\(l[i]\)無意義
有了上述四個剪枝,我們已經可以跑過\(64\)個點,但是在最後一個點\(T\)了
因此,我們再加最後一個信仰剪枝,即如果當前執行次數過多,直接輸出當前最優解,同時殺死程式
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005,maxk=21;
int s[maxn],l[maxn],n;
int gcd(int aa,int bb){
if(bb==0) return aa;
return gcd(bb,aa%bb);
}
int ans=0x3f3f3f3f;
int tim=0;
void dfs(int now,int cnt,int tot,int gys){
tim++;
if(tim>2e7){
if(ans==0x3f3f3f3f) printf("-1\n");
else printf("%d\n",ans);
exit(0);
}
if(gys==1) ans=min(ans,tot);
if(tot>=ans || cnt>9 || now>n) return;
for(int i=now;i<=n;i++){
if(gys==0) dfs(i+1,cnt+1,tot+s[i],l[i]);
else if(l[i]%gys!=0) dfs(i+1,cnt+1,tot+s[i],gcd(gys,l[i]));
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&l[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&s[i]);
}
int jud=l[1];
for(int i=2;i<=n;i++){
jud=gcd(jud,l[i]);
}
if(jud!=1){
printf("-1\n");
return 0;
}
dfs(1,0,0,0);
if(ans!=0x3f3f3f3f)printf("%d\n",ans);
else printf("-1\n");
return 0;
}
三、CF985E Pencils and Boxes
題目描述
給出\(n\)個整數\(a_1,a_2,...,a_n\),現在需要對其進行分組,使其滿足以下條件:
每個數都必須恰好分入一組中
每一組中必須至少包含\(k\)個數
在每一組中,整數的權值之差的絕對值不能超過\(d\)。即當\(a_i,a_j\)在同一組時,需要滿足\(|a_i+a_j| \leq d\)
請判斷是否存在滿足條件的分組方案,若有請輸出"YES",否則輸出"NO"。
資料範圍:\(1 \leq k \leq n \leq 5 * 10^5 , 0 \leq d \leq 10^9\)
分析
這道題我們要用到\(bool\)型別的\(DP\)
為了方便處理,我們將所有數從小到大排一下序
我們設\(f[i]\)為當前遍歷到第\(i\)個數,是否合法
其中值為\(1\)代表合法,值為\(0\)代表不合法
在轉移時需要列舉每一個左端點,如果左端點的狀態合法,我們再用當前的狀態不斷向右更新
最後如果\(f[n]\)為\(1\),那麼輸出YES
,否則輸出NO
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
bool f[maxn];
ll a[maxn];
int main(){
ll n,k,d;
scanf("%lld%lld%lld",&n,&k,&d);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
sort(a+1,a+1+n);
f[0]=1;
ll p=1;
for(ll i=0;i<=n;i++){
if(f[i]){
p=max(p,i+k);
while(p<=n && a[p]-a[i+1]<=d){
f[p]=1;
p++;
}
}
}
if(f[n]==0) printf("NO\n");
else printf("YES\n");
return 0;
}
四、CF1340B Nastya and Scoreboard
題目描述
分析
比較有意思的一道題
直接放個連結題解
程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
char num[10][10]={"1110111","0010010","1011101","1011011","0111010","1101011","1101111","1010010","11111111","1111011"};
int dp[N][N];
char a[N][10];
int cal(char c[],int pos){
int cnt=0;
for(int i=0;i<7;++i){
if(c[i]=='1' && num[pos][i]=='0') return -1;
if(c[i]!=num[pos][i]) cnt++;
}
return cnt;
}
int main(){
memset(dp,-1,sizeof(dp));
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
scanf(" %s",a[i]);
for(int i=n;i>=1;--i){
if(i==n){
for(int j=0;j<10;++j){
int t=cal(a[i],j);
if(t==-1) continue;
dp[n][t]=max(dp[n][t],j);
}
}
else{
for(int j=0;j<10;++j){
int t=cal(a[i],j);
if(t==-1) continue;
for(int p=t;p<=k;++p){
if(dp[i+1][p-t]!=-1){
dp[i][p]=j;
}
}
}
}
}
if(dp[1][k]==-1){
puts("-1");
return 0;
}
int now=k;
for(int i=1;i<=n;++i){
printf("%d",dp[i][now]);
now-=cal(a[i],dp[i][now]);
}
puts("");
return 0;
}