8.7日 Dp/贪心 专场模拟赛

 感觉挑的题都很好

T1   T2   T3   T4

 感觉我的字符串相比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;
}

 

posted @ 2021-08-07 10:53  Hehe_0  阅读(30)  评论(0)    收藏  举报