2017暑训摸底(xdoj1045,xdoj1173,xdoj1007,xdoj1038)

这篇博客回顾了2017暑训的几道dp题目,包括黑白棋的博弈搜索、The most orz man的sg值规律以及易碎的鸟蛋的典型dp解法。作者通过分析思路,展示了如何利用数学和动态规划解决问题,并在D题中提到有时枚举比二分更有效。最后提到了状压dp在裁玻璃问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于时间没赶上摸底,只能跟一波新增的dp摸底,感觉难度一般般,可能有段时间内没做题的缘故吧

暑训就要开始了呢

 

A 黑白棋

 

思路:一上来就是个博弈搜索

根据上一状态与这一状态必胜必败态的转换来判断先手的情况

dfs搜索所有前项状态,若均为必败态,则该状态为必胜态,否则为必败态

 

 

/*
Author:Owen_Q
*/

#include <bits/stdc++.h>

using namespace std;

int a[5][5];

int dfs(int x,int y)
{
    int next[2][4]={{0,0,1,-1},{1,-1,0,0}};
    int x1,y1,i,p=1;
    for(i=0;i<4&&p==1;i++)
    {
        x1=x+next[0][i];
        y1=y+next[1][i];
        if(x1>=0&&y1>=0&&x1<5&&y1<5&&a[x1][y1]==0)
        {
            a[x1][y1]=1;
            p=(dfs(x1,y1)+1)%2;
            a[x1][y1]=0;
        }
    }
    return p;
}

int main()
{
    int i,j,t;
    scanf("%d",&t);
    while(t--)
    {
        for(i=0;i<5;i++)
        {
            for(j=0;j<5;j++)
            {
                scanf("%1d",&a[i][j]);
            }
        }
        int p=0;
        for(i=0;i<5&&p==0;i++)
        {
            for(j=0;j<5&&p==0;j++)
            {
                if(a[i][j]==0)
                {
                    a[i][j]=1;
                    p=dfs(i,j);
                    a[i][j]=0;
                }
            }
        }
        if(p==0)
            printf("lose\n");
        else
            printf("win\n");
    }
    return 0;
}

 

 

 

 

 

 

B The most orz man

思路:sg值打表找规律

sg值:mex函数为下一状态的sg值,则该状态sg值为最小不属于mex的非负整数

这是个很巧妙的方法,成功利用数学方式构造了必胜必败态模型,通过计算实现结果的模拟

最终发现3的整数倍为必胜态,成功ac

 

 

/*
Author:Owen_Q
*/

#include <bits/stdc++.h>

using namespace std;
/*
const int maxn = 10010;

int sg[maxn];

void getSG(int a[],int n)//a为可能走法,n为所求sg数量
{
	int i,j;
	int temp[maxn];//存储前项sg值,用于求mex
	memset(sg,0,sizeof(sg));
	for(i=0;i<n;i++)//依次求sg值
	{
		memset(temp,0,sizeof(temp));
		for(j=0;i-a[j]>=0;j++)//j为可走步数
		{
			if(i<a[0])
				break;
			temp[sg[i-a[j]]]=1;
		}
		for(j=0;j<n;j++)
		{
			if(temp[j]==0)
			{
				sg[i]=j;
				break;
			}
		}
	}
}
*/
int main()
{
    int n;
    /*int a[maxn],t=1;
    for(int i=0;i<70;i++)
    {
        a[i]=t;
        t*=2;
        if(t<0)
        {
            break;
        }
    }*/
    while(scanf("%d",&n)!=EOF)
    {
        /*getSG(a,n);
        for(int j=0;j<n;j++)
        {
            printf("%d %d\n",j,sg[j]);
        }*/
        if(n%3==0)
        {
            printf("No\n");
        }
        else
        {
            printf("Yes\n");
        }
    }
    return 0;
}

 

 

 

D 易碎的鸟蛋

 

思路:典型的dp,以两个题设变量鸡蛋数n和楼层数m作为状态变量,以所求结果最少实验次数作为dp值,枚举投放鸡蛋的试验楼层

可得到两种情况,分为鸡蛋破碎和未破碎,于是一分为二

若鸡蛋破碎,则继续试验较低楼层,但可用鸡蛋数减一

若鸡蛋未破,则将上层楼层提取出来,成为研究楼高仅为上层楼层的子问题

两种情况中较高的试验次数作为该状态的dp值,枚举结果中取最低值进行转移

转移方程为dp[i][j]=min((max(dp[i-1][k-1],dp[i][j-k])+1),dp[i][j])

 

 

/*
Author:Owen_Q
*/

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e3+10;

int dp[maxn][maxn];

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int j=0;j<=1000;j++)
        {
            dp[0][j]=0;
            dp[1][j]=j;
            dp[j][0]=0;
            dp[j][1]=1;
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=2;j<=m;j++)
            {
                dp[i][j]=dp[i-1][j];
                for(int k=1;k<=j;k++)
                {
                    dp[i][j]=min((max(dp[i-1][k-1],dp[i][j-k])+1),dp[i][j]);
                }
            }
        }
        printf("%d\n",dp[n][m]);
    }
    return 0;
}

 

 

 

 

 

最终摸底的结果还是可以接受的,全场第三,d题一开始总想着二分找最优状态,后来发现不足10的数据量完全可以枚举,或许利用计算机才是acm正确的姿势吧

 

dp专题,怎么能没有状压呢,上次的c题就是一个典型的状压

C 裁玻璃

思路:按层转移,每层的状态只与其上一层状态有关,由于每层的列数很少,完全可以考虑用二进制数存储状态,状态压缩。特殊考虑无法放玻璃的位置,对于本层将其考虑为未放置,对于下层,将其考虑为已放置。

关键对于不可能成立的情况,可以直接将其赋值为-1e9,避免其被挑选到,每次筛选出满足的最大值作为转移结果。

最后,就是注意第一层初始条件需要单独考虑,就ok了

 

/*
Author:Owen_Q
*/

#include <bits/stdc++.h>

using namespace std;

const int maxn = 2000;

int row,n,t;

int dp[maxn][12];
bool good[12][12];


void cal(int k,int state)
{
    int add = 0;
    int realstate = state;
    for(int i=0;i<n;i++)
    {
        if(!good[k][i+1])
        {
            if(realstate&(1<<i))
            {
                realstate ^= (1<<i);
            }
            else
            {
                dp[state][k] = -1e9;
                return ;
            }
        }
    }
    //cout << k << "*" << state << "*****" << realstate << endl;
    //cout << good[k][1] << good[k][2] << good[k][3] << good[k][4] << endl;
    bool half = false;
    for(int i=0;i<n;i++)
    {
        if(realstate&(1<<i))
        {
            if(half)
            {
                half = false;
                add++;
            }
            else
            {
                half = true;
            }
        }
        else
        {
            if(half)
            {
                dp[state][k] = -1e9;
                return ;
            }
        }
    }
    if(k==1)
    {
        add = 0;
    }
    for(int s=0;s<=((1<<n)-1);s++)
    {
        int pos;
        for(pos=0;pos<n;pos++)
        {
            if((realstate&(1<<pos))&&(s&(1<<pos)))
            {
                break;
            }
        }
        if(pos==n&&((dp[s][k-1]+add)>dp[state][k]))
        {
            dp[state][k] = dp[s][k-1]+add;
        }
    }
    return ;
}

int main()
{
    //cout << (1<<10) << endl;
    while(scanf("%d",&t)!=EOF)
    {
        while(t--)
        {
            scanf("%d%d",&row,&n);
            memset(dp,0,sizeof(dp));
            memset(good,true,sizeof(good));
            for(int i=1;i<=row;i++)
            {

                for(int j=1;j<=n;j++)
                {
                    int temp;
                    scanf("%d",&temp);
                    if(temp == 0)
                    {
                        good[i][j] = false;
                    }
                }
            }
            for(int i=1;i<=row;i++)
            {

                for(int s=0;s<=((1<<n)-1);s++)
                {
                    cal(i,s);
                }
            }
            int maxblock = 0;
            for(int s=0;s<=((1<<n)-1);s++)
            {
                if(dp[s][row]>maxblock)
                {
                    maxblock = dp[s][row];
                }
            }
            printf("%d\n",maxblock);
            //cout << ((1<<3)|(1<<2)|(1<<1)|(1<<0)) <<"*"<<dp[(1<<3)|(1<<2)|(1<<1)|(1<<0)][2] << endl;
            /*for(int s=0;s<=((1<<n)-1);s++)
            {
                if(dp[s][1]>=0)
                {
                    cout << s << "**" << dp[s][1] << endl;
                }
            }*/
        }
    }
    return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值