省選題目選做(2)
阿新 • • 發佈:2022-04-05
省選題目選做(2)
\(T1\)訊號傳遞
算是把每一檔分都得了一遍...
第一步\(O(n\times2^m)\)比較好想,直接統計前後貢獻即可
按照一開始轉移方式每個點單獨轉移
//可以提前對於每個點要傳遞出去的點連邊 //然後轉移的時候,列舉這一步要新增哪個點 //貌似不太能處理位置 //考慮怎麼能夠每次轉移一個點求貢獻,不需要知道前面的位置 //考慮結論: //x<y (res+=y-x) //x>y (res+=kx+ky) //那麼這樣的話,考慮我們每個點記錄一下貢獻 //當前點連出的點有num1沒有放置的,就-num*x //當前點連出的點有num2已經放置的,就+num*k*x //當前點連入的點有num3沒有放置的,那麼就+num*k*x //當前點連入的點有num4已經放置的,那麼就+num*x //複雜度2^m*n? #define Eternal_Battle ZXK #include<bits/stdc++.h> #define MAXN 25 using namespace std; int dp[1<<23],S[MAXN],n,m,k; vector<int>rd[MAXN],fx[MAXN]; int lowbit(int x) { return x&(-x); } int Count(int x) { int res=0; while(x) { x-=lowbit(x); res++; } return res; } void Make(int x) { for(int i=0;i<n;i++) { cout<<((x>>i)&1); } cout<<" "; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) { scanf("%d",&S[i]); } for(int i=1;i<n;i++) { if(S[i]==S[i+1]) continue; rd[S[i]].push_back(S[i+1]); fx[S[i+1]].push_back(S[i]); } memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(int i=0;i<=(1<<m)-1;i++) { int x=Count(i)+1; for(int j=1;j<=m;j++) { if(((i>>(j-1))&1)==0) { int num1=0,num2=0,num3=0,num4=0; for(int k=0;k<rd[j].size();k++) { int y=rd[j][k]; if(((i>>(y-1))&1)==0) num1++; else num2++; } for(int k=0;k<fx[j].size();k++) { int y=fx[j][k]; if(((i>>(y-1))&1)==0) num3++; else num4++; } dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]-num1*x+num2*k*x+num3*k*x+num4*x); // Make(i|(1<<(j-1))); } } } cout<<dp[(1<<m)-1]; }
那麼比較容易優化到不列舉邊,而選擇列舉點,並且預處理貢獻\(O(m^22^m)\)
//可以提前對於每個點要傳遞出去的點連邊 //然後轉移的時候,列舉這一步要新增哪個點 //貌似不太能處理位置 //考慮怎麼能夠每次轉移一個點求貢獻,不需要知道前面的位置 //考慮結論: //x<y (res+=y-x) //x>y (res+=kx+ky) //那麼這樣的話,考慮我們每個點記錄一下貢獻 //當前點連出的點有num1沒有放置的,就-num*x //當前點連出的點有num2已經放置的,就+num*k*x //當前點連入的點有num3沒有放置的,那麼就+num*k*x //當前點連入的點有num4已經放置的,那麼就+num*x //複雜度2^m*n? #define Eternal_Battle ZXK #include<bits/stdc++.h> #define MAXN 25 using namespace std; int rd[MAXN][MAXN],fx[MAXN][MAXN]; int dp[1<<23],S[MAXN],n,m,k; int zy[24][1<<23]; int lowbit(int x) { return x&(-x); } int Count(int x) { int res=0; while(x) { x-=lowbit(x); res++; } return res; } void Make(int x) { for(int i=0;i<n;i++) { cout<<((x>>i)&1); } cout<<" "; } int main() { // freopen("dp1.in","r",stdin); // freopen("dp1.in","r",stdin); scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) { scanf("%d",&S[i]); } for(int i=1;i<n;i++) { if(S[i]==S[i+1]) continue; rd[S[i]][S[i+1]]++; fx[S[i+1]][S[i]]++; } memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(int j=1;j<=m;j++) { // cout<<"now: "<<j<<"\n"; for(int i=0;i<=(1<<m)-1;i++) { if(((i>>(j-1))&1)!=0) continue; int x=Count(i)+1; int num1=0,num2=0,num3=0,num4=0; for(int k=1;k<=m;k++) { if(((i>>(k-1))&1)==0) num1+=rd[j][k],num3+=fx[j][k]; else num2+=rd[j][k],num4+=fx[j][k];; } // for(int k=0;k<rd[j].size();k++) // { // int y=rd[j][k]; // if(((i>>(y-1))&1)==0) num1++; // else num2++; // } // for(int k=0;k<fx[j].size();k++) // { // int y=fx[j][k]; // if(((i>>(y-1))&1)==0) num3++; // else num4++; // } zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x; } } // return 0; for(int i=0;i<=(1<<m)-1;i++) { // int x=Count(i)+1; for(int j=1;j<=m;j++) { if(((i>>(j-1))&1)==0) { // int num1=0,num2=0,num3=0,num4=0; // for(int k=0;k<rd[j].size();k++) // { // int y=rd[j][k]; // if(((i>>(y-1))&1)==0) num1++; // else num2++; // } // for(int k=0;k<fx[j].size();k++) // { // int y=fx[j][k]; // if(((i>>(y-1))&1)==0) num3++; // else num4++; // } // cout<<zy[j][i]<<" "<<-num1*x+num2*k*x+num3*k*x+num4*x<<"\n"; dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+zy[j][i]); } } } cout<<dp[(1<<m)-1]; } //21467158
第三部考慮預處理的過程也是可以遞推的
每次只需要新增多出來的貢獻就好了
大概就是一開始假設全在後面,然後一個個往前移動,處理一下,複雜度\(O(m2^m)\)
按照原來看到的技巧,本位不放數字,左右中間移動可優化空間複雜度
//可以提前對於每個點要傳遞出去的點連邊 //然後轉移的時候,列舉這一步要新增哪個點 //貌似不太能處理位置 //考慮怎麼能夠每次轉移一個點求貢獻,不需要知道前面的位置 //考慮結論: //x<y (res+=y-x) //x>y (res+=kx+ky) //那麼這樣的話,考慮我們每個點記錄一下貢獻 //當前點連出的點有num1沒有放置的,就-num*x //當前點連出的點有num2已經放置的,就+num*k*x //當前點連入的點有num3沒有放置的,那麼就+num*k*x //當前點連入的點有num4已經放置的,那麼就+num*x //複雜度2^m*n?行吧,我已經快吐了 //30->70->100,極致優化過程唄 #define Eternal_Battle ZXK #include<bits/stdc++.h> #define MAXN 25 using namespace std; int dp[1<<23],id[1<<23],S[MAXN],n,m,k; int cnt[MAXN][MAXN]; int zy[24][1<<23]; int lowbit(int x) { return x&(-x); } int Count(int x) { int res=0; while(x) { x-=lowbit(x); res++; } return res; } void Make(int x) { for(int i=0;i<n;i++) { cout<<((x>>i)&1); } cout<<" "; } int main() { // freopen("dp1.in","r",stdin); // freopen("dp1.in","r",stdin); scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) { scanf("%d",&S[i]); } for(int i=1;i<n;i++) { if(S[i]==S[i+1]) continue; cnt[S[i]][S[i+1]]++; } for(int i=1;i<=m;i++) { for(int j=1;j<=m;j++) { if(j==i) continue; zy[i][0]+=-cnt[i][j]; zy[i][0]+=cnt[j][i]*k; } } for(int i=0;i<=m;i++) { id[1<<i]=i+1; } for(int i=1;i<=m;i++) { for(int j=1;j<(1<<(m-1));j++) { int val=lowbit(j),poz=id[val]; if(poz>=i) poz++; zy[i][j]=zy[i][j^val]+cnt[poz][i]+cnt[i][poz]*k+cnt[i][poz]-cnt[poz][i]*k; // cout<<"zy: "<<i<<" "<<j<<" "<<zy[i][j]<<"\n"; } } memset(dp,0x3f,sizeof(dp)); dp[0]=0; // for(int j=1;j<=m;j++) // { //// cout<<"now: "<<j<<"\n"; // for(int i=0;i<=(1<<m)-1;i++) // { // if(((i>>(j-1))&1)!=0) continue; // int x=Count(i)+1; // int num1=0,num2=0,num3=0,num4=0; // for(int k=1;k<=m;k++) // { // if(((i>>(k-1))&1)==0) num1+=rd[j][k],num3+=fx[j][k]; // else num2+=rd[j][k],num4+=fx[j][k]; // } // zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x; // } // } // return 0; for(int i=0;i<=(1<<m)-1;i++) { int x=Count(i)+1; // cout<<i<<" "<<dp[i]<<"\n"; for(int j=1;j<=m;j++) { if((i>>(j-1)&1)==0) { // int num1=0,num2=0,num3=0,num4=0; // for(int k=0;k<rd[j].size();k++) // { // int y=rd[j][k]; // if(((i>>(y-1))&1)==0) num1++; // else num2++; // } // for(int k=0;k<fx[j].size();k++) // { // int y=fx[j][k]; // if(((i>>(y-1))&1)==0) num3++; // else num4++; // } // cout<<zy[j][i]<<" "<<-num1*x+num2*k*x+num3*k*x+num4*x<<"\n"; int pS=i%(1<<j-1); dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+x*zy[j][(i-pS)/2+pS]); } } } cout<<dp[(1<<m)-1]; } //21467158
\(T2\)春節十二響
詢問最小的分配方案使得每一部分的記憶體和最小
如果我想的話,貪心是必然的,那麼最大的肯定會選,那麼從大往小列舉,被迫新開的時候再開就好了,必然是最優的
考慮證明,假設我們存在一種方案,使得目前最大即使能放入一個仍重開一個會更優
那麼唯一可能的情況就是後面的多一部分進入前面的這個,需要滿足的條件是\(zx[new]=now,zx[new]!=ls,zx[now]!=ls\)其實發現這種情況,即使\(x\)放進去,\(new\)依舊新開,那麼\(x\)放進去顯然更優
到這裡期望得分\(45pts\)
還是考慮我們合併的過程,同一個子樹內,我們可以分成若干個堆目前
那麼我們合併兩個子樹,比較優的是,讓兩個大的互相傷害,然後放入目前節點堆裡即可
至於合併的話,啟發式合併可以做到\(O(nlogn)\)
然後實際程式碼很好寫了
#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define int long long
#define MAXN 200005
using namespace std;
int head[MAXN],nxt[MAXN],to[MAXN],tot;
priority_queue<int>q[MAXN];
int Me[MAXN],n;
void add(int u,int v)
{
tot++;
to[tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void Merge(int x,int y)
{
if(q[x].size()<q[y].size()) swap(q[x],q[y]);
priority_queue<int>Mid;
while(q[y].size())
{
Mid.push(max(q[x].top(),q[y].top()));
q[x].pop();
q[y].pop();
}
while(Mid.size())
{
q[x].push(Mid.top());
Mid.pop();
}
}
void dfs(int now)
{
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
dfs(y);
Merge(now,y);
}
q[now].push(Me[now]);
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&Me[i]);
}
for(int i=2,fa;i<=n;i++)
{
scanf("%lld",&fa);
add(fa,i);
}
dfs(1);
int Ans=0;
while(q[1].size())
{
Ans+=q[1].top();
q[1].pop();
}
cout<<Ans<<"\n";
}