省选题目选做(2)
省选题目选做(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";
}