8.7日 Dp/贪心 专场模拟赛
感觉挑的题都很好
感觉我的字符串相比dp还是弱多了
[SDOI2012]吊灯
大致题意:我们需要在一个树上改变9次树的形态(有固定规则,题面已给出,可详见代码),每次我们需要使相同颜色相连(分组),每个联通块大小相等。求每种形态树满足要求的组大小。
显然,我们的这个数一定是总结点的约数。
我们可以考虑去找每个块的大小,比如为 $ x $
注意:一定存在 $ \left(\dfrac{n}{x}\right) $ 其子树大小为x的倍数。
我们可以很暴力的枚举 1~n ,看:1.它是不是约数 2.放到树上是否可行
每次枚举后树的形态都会改变,记得重新处理siz,num
#include<bits/stdc++.h> using namespace std; const int N=12e5+7; int fa[N],siz[N],num[N]; int n; inline int read() { int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();} while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } void init() { fill(num,num+1+n,0); fill(siz,siz+1+n,1); for(int i=n;i;i--) { siz[fa[i]]+=siz[i]; num[siz[i]]++; } return ; } bool pd(int x) { int re=0; for(int i=x;i<=n;i+=x) {//x的倍数大小的数量 re+=num[i]; } return re==n/x; } int main() { ios::sync_with_stdio(false); n=read(); for(int i=2;i<=n;i++) fa[i]=read(); for(int cas=1;cas<=10;cas++) { cout<<"Case #"<<cas<<':'<<endl; init(); for(int i=1;i<=n;i++) { if((!(n%i))&&pd(i)) cout<<i<<endl; } for(int i=2;i<=n;i++) fa[i]=(fa[i]+19940105)%(i-1)+1; } return 0; }
[NOI2014] 随机数生成器
可以考虑画图找规律,就嗯模拟+贪心即可
需要注意:卡内存
#include<bits/stdc++.h> #define rg register using namespace std; const int N=25000007; inline int read() { rg int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();} while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } int n,m,t,x[N],y[N],l[5007],r[5007],len; int main() { ios::sync_with_stdio(false); x[0]=read(); rg int a=read(),b=read(),c=read(),d=read(); n=read(),m=read(); len=n*m; t=read(); for(rg int i=1;i<=len;i++) x[i]=(1ll*a*x[i-1]%d*x[i-1]%d+1ll*b*x[i-1]%d +c)%d; for(rg int i=1;i<=len;i++) y[i]=i; for(rg int i=1;i<=len;i++) swap(y[i],y[x[i]%i+1]); for(rg int i=1;i<=t;i++) { rg int q,w; q=read(),w=read(); swap(y[q],y[w]); } for(rg int i=1;i<=len;i++) x[y[i]]=i; fill(l+1,l+1+n,1); fill(r+1,r+1+n,m); for(rg int i=1,tot=0;i<=len&& tot<n+m;i++) { rg int q=(x[i]+m-1)/m,w=x[i]%m; if(!w) w += m; if(l[q]<=w&&r[q] >=w) { tot++; cout<<i; if(tot!=n+m-1) cout<<" "; for(rg int j=q+1;j<=n;j++) l[j]=max(l[j],w); for(rg int j=1;j<q;j++) r[j]=min(r[j],w); } } return 0; }
[POI2008]MAF-Mafia
很容易想到建图,而且有方向性和前后关系
但是我们可直接找规律
1.自杀者直接没救暴毙掉
2.入读为零直接开枪
3.如果成环的话,想要多杀按顺序开枪,剩一个想要少杀,隔一个开一个,剩一半
考场思路(由于每个人只有开枪和被打死两种选项,所以我在想随机化可否,然。。
#include<bits/stdc++.h> using namespace std; const int N=1e6+7; int n; inline int read() { int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();} while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } int a[N],ind[N]; int ans1,ans2; int q[N],num[N]; bool flag[N]; int main() { ios::sync_with_stdio(false); n=read(); for(int i=1;i<=n;i++) { a[i]=read(); ind[a[i]]++; } for(int i=1;i<=n;i++) { if(!ind[i]) { ans1++;ans2++; q[ans2]=i; } } for(int i=1;i<=ans2;i++) { int x=a[q[i]]; if(flag[x]) continue; flag[x]=1; num[a[x]]=1; ind[a[x]]--; if(!ind[a[x]]) q[++ans2]=a[x]; } for(int i=1;i<=n;i++) { if(ind[i]&&!flag[i]) { int len=0,x=0; for(int j=i;!flag[j];j=a[j]) { flag[j]=1; len++; x|=num[j]; } if(!x&&len>1) ans1++; ans2+=len/2; } } cout<<n-ans2<<' '<<n-ans1; return 0; }
[SCOI2008] 奖励关
我们观察数据范围: $ n \le 15 $ , 我们不难想到状态压缩
又由于我们取东西是有前后时间关系,也有选择的限制,所以我们将物品所选的状态压起来。
用 $ dp [ i ] [ j ] $ 表示 我们在第 $i$ 轮的所取过物品的状态为 $ j $ 所的最大值(期望
接下来我们思考转移方程:
我们在考虑一个物品时有两个状态
1.我们没有达到物品的要求,我们没有办法在此轮拿它
2.我们达到了要求,我们在 $ dp [ i ] [ j ] $ 要去找 $ dp [ i+1 ] [ j ] $ 和 $dp [ i + 1 ] [ 1 << k - 1 ] $
如果2情况不太会用代码实现的状压小白可以手玩一些数据
比如
我们现有状态为 **1011001**
.............而物品限制为 **1001011**
然后 & 出来的结果是 **1001001**
所以这个物品不可选
#include<bits/stdc++.h> using namespace std; const int N=105; int n,m; //n个物品m轮 //第i轮状态为j的最大收益 double dp[N][1<<15|1]; int a[N];//每个物品的值 int limit[N];//第i个物品的限制状态 int main() { ios::sync_with_stdio(false); cin>>m>>n; for(int i=1;i<=n;i++) { cin>>a[i]; int flag; while(cin>>flag&&flag) limit[i]|=(1<<flag-1); //把当前物品的限制压到数组里 } for(int i=m;i>=1;i--) {//轮的次数,这里要倒着来 for(int j=0;j<=(1<<n);j++) {//状态 for(int k=1;k<=n;k++) {//枚举所扔出来的物品 if((j&limit[k])>=limit[k]) {// 达到了要求 dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<k-1)]+a[k]); } else//没有达到要求 dp[i][j]+=dp[i+1][j]; } //由于我们需要求期望,并且有n个物品 dp[i][j]/=n; } } //控一下小数位, printf("%.6lf",dp[1][0]); return 0; }