高考集训讲课(To 高一)
作者:互联网
主要是怕下午讲着讲着把自己讲懵了,有一定的迷糊概率
经过机房的讨论,一致认为插头\(DP\)实用性不大,所以这次不讲了,先把重要的讲一讲。
顺便吐槽一下,凭什么另外几个人都是几个相互联系的知识点,到我这跨越这么大。。。
反正都是\(trick\)直接上题,没有知识点讲
状压\(DP\)
这是上次想讲的题
费用提前\(+\)状压\(DP\)
我们最后只求情况数,那么中间每个队伍的通过题数是无需关注的
从最开始考虑,我们暴力枚举每种情况,如何判断这种情况能否满足
我们有序列\(a_1,a_2...a_n\),我们的要求是,每次\(a_{i-1}+b_{i-1}<a_i+b_i\),并且\(b_i>b_i-1\),只需要贪心的扫一遍维护一下即可,保证每次确定的\(b_i\)尽可能小
考虑状压,\(dp[i][j][k]\)表示我们已经确定\(i\)状态的队伍分数,最后一个确定的是\(j\),我们已经使用的题数是\(j\)
由于我们上一个都比前一个多,我们直接把后面的都加上一个这个数的值就好了(费用提前)
转移易得
//有个东西叫费用提前
//当你枚举这个选了多少个的时候,为了保证后面的也是
//那么后面的就统一加上,然后那么这个时候差不变
//该加多少还是加多少
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[1<<13][15][505],a[15],Ans,n,m;
int lowbit(int x)
{
return x&(-x);
}
int Count(int num)
{
int res=0;
while(num)
{
res++;
num-=lowbit(num);
}
return res;
}
signed main()
{
cin>>n>>m;
int Max=0;
a[0]=-1;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]>a[Max])
{
Max=i;
}
}
for(int i=1;i<=n;i++)
{
int tar=n*(a[Max]-a[i]+(Max<i));
if(tar<=m)
{
dp[1<<(i-1)][i][tar]=1;
}
}
for(int i=1;i<=(1<<n)-1;i++)
{
int Num=Count(i);
for(int j=1;j<=n;j++)
{
if((i>>(j-1))&1)
{
for(int k=0;k<=m;k++)
{
for(int z=1;z<=n;z++)
{
if(!((i>>(z-1))&1))
{
int tar=k+(n-Num)*max(0ll,a[j]-a[z]+(j<z));
if(tar<=m)
dp[i|(1<<(z-1))][z][tar]+=dp[i][j][k];
}
}
}
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
Ans+=dp[(1<<n)-1][i][j];
}
}
cout<<Ans<<endl;
}
比较套路的贡献分开计算
设我们目前放的点是\(x\)
\(x<y,res+=(y-x)\)
\(x>y,res+=(kx+ky)\)
那么我们可以处理每个位置时候计算一下贡献就好了
可以参考一下注释
如果直接枚举边\(30pts\),提前统计一下的话是\(70pts\)
\(30pts:\)
//可以提前对于每个点要传递出去的点连边
//然后转移的时候,枚举这一步要新增哪个点
//貌似不太能处理位置
//考虑怎么能够每次转移一个点求贡献,不需要知道前面的位置
//考虑结论:
//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];
}
\(70pts\)
考虑预处理转移\(zy[j][i]\)
表示\(j\)点在\(i\)状态转移会有多少代价
#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];;
}
zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x;
}
}
for(int i=0;i<=(1<<m)-1;i++)
{
for(int j=1;j<=m;j++)
{
if(((i>>(j-1))&1)==0)
{
dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+zy[j][i]);
}
}
}
cout<<dp[(1<<m)-1];
}
我们发现预处理复杂度有点高,那就考虑递推预处理
由于我们被卡空间,考虑把中间没用的那一位抹去左右拼接即可
#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;
}
}
memset(dp,0x3f,sizeof(dp));
dp[0]=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 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
计数
常见的话有\(DP\)计数,生成函数计数(后面这个和多项式有关,就交给\(HH\)力,我就不管了)
\(CF724F\ Uniformly\ Branched\ Trees\)
无论是什么计数,最开始的出发点就是找特征点
树上计数,每棵树最特殊的位置就是重心,因为重心只有\(1/2\)个
考虑组合问题,在\(x\)方案里面选\(t\)个,可重复
答案是\(\binom{x+t-1}{t}\)
感性想一想就是,我们每次选一个,在后面加一个方案表示,下一次可以选和这个一样的,然后我们相当于加了\(t-1\)个方案,一共在这些里面选\(t\)个
\(dp[i][j][k]\)表示节点数为\(i\),有\(j\)棵子树,子树大小都不超过\(k\),并且根是重心的树的数目
首先考虑单重心的情况\(n\%2=1\)
我们转移的时候,最外层枚举节点数\(i\),内层枚举子树\(j\),再枚举不超过节点数\(k\),再枚举节点\(k\)
转移易得
\(dp[i][j][k]+=dp[i-t\times k][j-t][k-1]\times C(dp[k][d-1][k-1]+t-1,t)\)
然后双中心会算重,减去一部分就好了
\(Ans-=C(dp[n/2][d-1][n/2-1],2)\)
#include<bits/stdc++.h>
#define int long long
#define MAXN 1005
using namespace std;
int fac[MAXN+5],inv[MAXN+5];
int dp[MAXN][20][MAXN],n,d,mod;
void Init()
{
fac[0]=inv[0]=1;
fac[1]=inv[1]=1;
for(int i=2;i<=MAXN;i++)
{
fac[i]=fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
for(int i=1;i<=MAXN;i++)
{
inv[i]=(inv[i-1]*inv[i])%mod;
}
}
int C(int n,int m)
{
if(n<m) return 0;
int res=1;
for(int i=1;i<=m;i++) res=(res*(n-i+1))%mod;
return res*inv[m]%mod;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main()
{
scanf("%lld%lld%lld",&n,&d,&mod);
Init();
if(n<=2)
{
cout<<1;
return 0;
}
for(int i=0;i<=n;i++) dp[1][0][i]=1;//节点数1,0子树,子树大小不超过i
for(int i=1;i<=n;i++)
{
for(int j=1;j<=min(i-1,d);j++)
{
for(int k=1;k<=n;k++)
{
dp[i][j][k]=dp[i][j][k-1];
for(int t=1;i>t*k&&j-t>=0;t++)
{
if(k!=1) dp[i][j][k]=(dp[i][j][k]+dp[i-t*k][j-t][k-1]*C(dp[k][d-1][k-1]+t-1,t)%mod)%mod;
else dp[i][j][k]=(dp[i][j][k]+dp[i-t*k][j-t][k-1]*C(dp[k][0][k-1]+t-1,t)%mod)%mod;
}
}
}
}
if(n%2) cout<<dp[n][d][n/2]<<"\n";
else cout<<(dp[n][d][n/2]+mod-C(dp[n/2][d-1][n/2-1],2))%mod;
}
这是一道练习题
树形\(dp\),换根\(dp\)
考场上想出正解,忘加\(ull\)丢掉\(40pts\)的一道题,我和题解
考虑暴力枚举哪个点是多余的,把剩下的部分树\(hash\)一下,判断是否一样即可,复杂度\(O(n^2)\)
考虑我们对第一棵树以每个根都\(hash\)一次,换根\(dp\)转移一下
第二棵树同样,遇到叶子时候查一下有没有这个树就好了
#include<bits/stdc++.h>
#define ull unsigned long long
#define MAXN 200005
using namespace std;
ull hasha[MAXN],hash1[MAXN],Mid[MAXN];
int head[MAXN],nxt[MAXN],to[MAXN],Ans=MAXN,tot;
int pri[MAXN],siz[MAXN],du[MAXN],cnt;
vector<int>rd[MAXN];
bool vis[MAXN*10];
map<ull,bool>mp;
void add(int u,int v)
{
tot++;
to[tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void Init()
{
for(int i=2;i<=MAXN*10-5;i++)
{
if(!vis[i])
{
pri[++cnt]=i;
}
for(int j=1;j<=cnt&&i*pri[j]<=MAXN*10-5;j++)
{
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
}
void dfs(int now,int fa)
{
siz[now]=1;
hasha[now]=1ull;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(y==fa) continue;
dfs(y,now);
siz[now]+=siz[y];
hasha[now]+=hasha[y]*pri[siz[y]];
}
}
void dfs_gen(int now,int fa)
{
int now_siz=siz[now];
ull now_hash=hasha[now];
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(y==fa) continue;
hasha[now]-=(1ull*hasha[y]*pri[siz[y]]);
siz[now]-=siz[y];
siz[y]+=siz[now];
hasha[y]+=hasha[now]*pri[siz[now]];
Mid[y]=hasha[y];
mp[hasha[y]]=true;
dfs_gen(y,now);
siz[now]=now_siz;
hasha[now]=now_hash;
}
}
void dfs_b(int now,int fa)
{
siz[now]=1;
hash1[now]=1ull;
for(int i=0;i<rd[now].size();i++)
{
int y=rd[now][i];
if(y==fa) continue;
dfs_b(y,now);
siz[now]+=siz[y];
hash1[now]+=hash1[y]*pri[siz[y]];
}
}
void dfs_genb(int now,int fa)
{
int now_siz=siz[now];
ull now_hash=hash1[now];
for(int i=0;i<rd[now].size();i++)
{
int y=rd[now][i];
if(y==fa) continue;
if(du[y]==1)
{
if(mp[hash1[now]-pri[1]])
{
Ans=min(Ans,y);
}
}
}
for(int i=0;i<rd[now].size();i++)
{
int y=rd[now][i];
if(y==fa) continue;
hash1[now]-=(1ull*hash1[y]*pri[siz[y]]);
siz[now]-=siz[y];
siz[y]+=siz[now];
// cout<<"y: "<<y<<" "<<hash1[y]<<" ";
hash1[y]+=hash1[now]*pri[siz[now]];
// cout<<hash1[y]<<endl;
dfs_genb(y,now);
if(du[now]==1&&now==1)
{
if(mp[hash1[y]-pri[1]])
{
Ans=min(Ans,now);
}
}
siz[now]=now_siz;
hash1[now]=now_hash;
}
}
int n,u,v;
int main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
scanf("%d",&n);
Init();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,1);
Mid[1]=hasha[1];
dfs_gen(1,1);
// for(int i=1;i<=n;i++)
// {
// cout<<"rt: "<<i<<" "<<Mid[i]<<endl;
// }
n++;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
rd[u].push_back(v);
rd[v].push_back(u);
du[u]++;
du[v]++;
}
dfs_b(1,1);
dfs_genb(1,1);
cout<<Ans<<endl;
}
\(wqs\)二分
这东西挺重要的
大部分问题就是取恰好\(k\)个的问题
使用的前提是答案函数呈凸性
考虑选\(i\)个最优代价是\(f(i)\),我们通过二分额外代价\(k\),让多选一次的代价\(+k\),那么我们最后选\(i\)个代价就是\(f(i)-k\times i\),我们关于这个找到一个最大点,保证了是选这些的最大值,因为得到的点截距最大
模板题,直接二分\(k\)跑\(kruskal\)
#include<bits/stdc++.h>
#define INF 100000005
#define MAXN 500005
using namespace std;
inline int re()
{
int x = 0, p = 1;
char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * p;
}
struct node
{
int u,v,w,col;
}rd[MAXN];
int n,m,num,Ans,fa[MAXN];
bool cmp(node a,node b)
{
if(a.w==b.w) return a.col<b.col;
return a.w<b.w;
}
int Find(int x)
{
if(fa[x]==x) return x;
return fa[x]=Find(fa[x]);
}
int check(int mid)
{
for(int i=1;i<=m;i++)
{
if(rd[i].col==0) rd[i].w+=mid;
}
sort(rd+1,rd+1+m,cmp);
int cnt=0,Sum=0;
for(int i=0;i<n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int fu=Find(rd[i].u);
int fv=Find(rd[i].v);
if(fu==fv) continue;
fa[fu]=fv;
cnt+=(rd[i].col==0?1:0);
Sum+=rd[i].w;
}
for(int i=1;i<=m;i++)
{
if(rd[i].col==0) rd[i].w-=mid;
}
if(cnt>=num)
{
Ans=Sum-num*mid;
return 0;
}
return -1;
}
int main()
{
scanf("%d%d%d",&n,&m,&num);
for(int i=1;i<=m;i++)
{
rd[i].u=re();
rd[i].v=re();
rd[i].w=re();
rd[i].col=re();
}
int l=-111,r=111,res,mid;
while(l<=r)
{
mid=(l+r)>>1;
res=check(mid);
if(res==0)
{
l=mid+1;
}
else r=mid-1;
}
cout<<Ans<<"\n";
}
式子很好推。
说人话就是:分\(m\)段,每段价值为\((1+\sum_{i=1}^na_i)^2\)
\(emm\),突然想到,这道题需要斜率优化,貌似还没讲。。。先咕了。
同余最短路
我在苏州集训学到的神奇东西,所以说是我带到机房的
题都比较简单,就口胡吧
标签:int,讲课,++,枚举,MAXN,高一,集训,dp,define 来源: https://www.cnblogs.com/Eternal-Battle/p/16358866.html