2022-02-18 ~ 2022-02-20 周末总结

感觉自己前几天没做什么题(晚上又忘了写博客...鸽了鸽了

2022-02-18

牛客竞赛动态规划专题 概率DP

恶意竞争

已知的条件是如果已有s个子组件和n类漏洞,那么不需要任何的一个步骤。即 :
dp[s][n] = 0;
那么已知这个状态,我们可以做如下转移:
dp[i][j] = dp[i][j] * (i / s) * (j / n) + dp[i + 1][j] * ((s - i) / s) * (j / n) + dp[i][j + 1] * (i / s) * ((n - j) / n) + dp[i + 1][j + 1] * ((s - i) / s) * ((n - j) / n);
由于dp[i][j]在等式右边已经存在了,那么我们只需要将右边的dp[i][j]左移即可进行正常的转移
AC代码:

点击查看代码
#include <iostream>
using namespace std;
const int MAXN = 1003;
double dp[MAXN][MAXN];
int n,s;
double dfs(int x,int y)
{
	if(x > n || y > s)	return 0;
	if(x == 0 && y != 0)	return 0;
	if(x != 0 && y == 0)	return 0;
	if(dp[x][y] != -1)	return dp[x][y];
	
	double ans = 0;
	if(x == 0 && y == 0)
	ans += (dfs(x + 1,y + 1) + 1);
	else
	{
		int i = x,j = y;
		ans = x * y;
		ans += ((dfs(x + 1,y) + 1) * (n - i) * j);
		ans += ((dfs(x,y + 1) + 1) * (s - j) * i);
		ans += ((dfs(x + 1,y + 1) + 1) * (n - i) * (s - j));
        ans /= (n * s - i * j);
	}
	return dp[x][y] = ans;
}
int main()
{
	scanf("%d %d",&n,&s);
	for(int i = 0;i <= n;++i)
	for(int j = 0;j <= s;++j)	dp[i][j] = -1;
	dp[n][s] = 0;
	printf("%.10f\n",dfs(0,0));
}

带富翁

筛子只有6点,那么当我们当前状态进行到了n的位置就不需要往后再进行移动了,这时的答案就是a[n];
我们可以设dp[i]为当前在第i个,最终到达第n个地方的期望得分;
枚举每次可能的筛子点数,进行相应的转移,具体如下:
dp[i] += dp[i + k] (k∈[1,6])
dp[i] /= cnt (cnt += (i + k <= n));
dp[i] += a[i];
AC代码:

点击查看代码
#include <iostream>
using namespace std;
const int MAXN = 107;
int a[MAXN];
double dp[MAXN];
int n;
double dfs(int x)
{
	if(x > n)	return 0;
	if(dp[x] != -1)	return dp[x];
	
	double ans = 0;
	int cnt = 0;
	for(int i = 1;i <= 6;++i)
	{
		ans += dfs(x + i);
		cnt += (x + i <= n ? 1 : 0);
	}
	
	return dp[x] = ans / cnt + a[x];
}
int main()
{
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	for(int i = 0;i <= n;++i)	dp[i] = -1;
	
	dp[n] = a[n];
	printf("%lf\n",dfs(1));
	return 0;
}

筛子游戏

首先可以知道最后的结束状态就是x > n的时候可以不需要次数即:
i > n时:dp[i] = 0;
即dp[i]是从分数为i到分数 > n所需要的期望步数
那么观察一下之前的状态怎么转移到这个状态:
dp[i] = sum(dp[i + k] * p[k]) + dp[0] * p[0] + 1
k是挡墙筛子所摇出来的总点数,即k ∈ [3,k1 + k2 + k3];
但是右边有dp[0]这个状态,正是我们需要求的,显然破坏了dp的转移,因此需要找到另外一种方式转移:
我们将式子写成下面这样:
dp[i] = A[i] * dp[0] + B[i];
dp[i] = sum(A[i + k] * dp[0] * p[k] + B[i + k] * p[k]) + dp[0] * p[0] + 1;
dp[i] = (sum(A[i + k] * p[k]) + p[0]) * dp[0] + sum(B[i + k] * p[k]) + 1;
A[i] = (sum(A[i + k] * p[k]) + p[0]);
B[i] = sum(B[i + k] * p[k]) + 1;
dp[0] = dp[0] * A[0] + B[0];
只要求出来A[0]和B[0]就可直接求出dp[0]啦
而A、B的转移都是线性的~
AC代码:

点击查看代码
#include <iostream>
using namespace std;
const int MAXN = 607;
double A[MAXN],B[MAXN];
double p[MAXN];
int k1,k2,k3,a,b,c;
int n;
double dfs(int x)
{
	if(x > n)	return 0;
	if(A[x] != -1)	return A[x];
	
	double ans = p[0];
	for(int i = 3;i <= k1 + k2 + k3;++i)
	ans += p[i] * dfs(i + x);
	return A[x] = ans;
}
double dfs1(int x)
{
	if(x > n)	return 0;
	if(B[x] != -1)	return B[x];
	
	double ans = 1;
	for(int i = 3;i <= k1 + k2 + k3;++i)
	ans += p[i] * dfs1(i + x);
	return B[x] = ans;
}
int main()
{
	scanf("%d %d %d %d %d %d %d",&n,&k1,&k2,&k3,&a,&b,&c);
	for(int i = 0;i < MAXN;++i)	A[i] = B[i] = -1;
	
	double pp = 1.0 / k1 / k2 / k3;
	for(int i = 1;i <= k1;++i)
	for(int j = 1;j <= k2;++j)
	for(int k = 1;k <= k3;++k)
	{
		if(i == a && j == b && k == c)
		p[0] += pp;
		else
		p[i + j + k] += pp;
	}
	for(int i = n + 1;i <= n - 1 + k1 + k2 + k3;++i)
	A[i] = B[i] = 0;
	
	dfs(0);//A[i] = sum(A[i + x] * p[i]) + p[0];
	dfs1(0);//B[i] = sum(B[i + x] * p[i]) + 1;
	double ans = B[0] / (1 - A[0]);
	printf("%.10lf\n",ans);
	return 0;
}

食堂

由题目很容易设状态为dp[i][j]表示序列长度为i,当前位于j会排在队伍前k位的概率
j == 1时:
dp[i][1] = dp[i][i] * p2 + dp[i][1] * p1 + p4;
否则:
dp[i][j] = dp[i][j] * p1 + dp[i][j - 1] * p2 + dp[i - 1][j - 1] * p3 + p4 * (j <= k ? 1 : 0);
可以发现这里是都不能直接转移的,因此我们需要对式子进行进一步的简化
首先dp[i][1]和dp[i][j]是可以向左边合并的,然后
假设当前dp[i - 1]所有答案我们都已经知道了,那么:
p4和dp[i - 1][j - 1] * p3 + p4 * (j <= k)就是已知的,我们将它称为c[i];
那么dp[i][1] = dp[i][i] * p12 + c[1];
dp[i][j] = dp[i][j - 1] * p12 + c[j];
发现这其实构成了一个环,只需要求出其中一个答案,就可以线性得到其他答案。
很显然我们是可以对这个式子进行解方程的,只需要让两边都是相同的未知数即可
如:
dp[i][i] = dp[i][i - 1] * p12 + c[i];
dp[i][i] = (dp[i][i - 2] * p12 + c[i - 1]) * p12 + c[i];
...
dp[i][i] = (((dp[i][1] * p12 + c[2]) * p12 + c[3])...) * p12 + c[i];
再将dp[i][1]代为dp[i][i] * p12 + c[1]即可求解dp[i][i]~
AC代码:

点击查看代码
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
const int MAXN = 2007;
const double eps = 1e-10;
int n,m,k;
double p1,p2,p3,p4;
double dp[MAXN][MAXN];
double p12[MAXN],p14,p13;
/*
1 1 1 0.372818 0.318286 0.220035 0.0888615
*/
double dfs(int x,int y)
{
	if(y > x)	return 0;
 	else if(x == 0 || y == 0)	return 0;
	if(dp[x][y] != -1)	return dp[x][y];
	
	double ans = 0;
	if(y == 1)
	{
		ans += p4;
		double c = 0;
		for(int i = 1;i <= x;++i)
		{
			if(i == 1)
			c += (p14 * p12[x - i]);
			else if(i <= k)
			c += (dfs(x - 1,i - 1) * p13 + p14) * p12[x - i];
			else
			c += (dfs(x - 1,i - 1) * p13 * p12[x - i]);
		}
		dp[x][x] = c / (1 - p12[x]);
		ans += dp[x][x] * p2;
		return dp[x][y] = ans / (1 - p1);
	}
	ans += dfs(x,y - 1) * p2;
	ans += dfs(x - 1,y - 1) * p3;
	if(y <= k)	ans += p4;
	return dp[x][y] = ans / (1 - p1);
}
double c[MAXN];
int main()
{
	while(~scanf("%d %d %d %lf %lf %lf %lf",&n,&m,&k,&p1,&p2,&p3,&p4))
	{
		if(fabs(p4) < eps)
		{
			puts("0.00000");
			continue;
		}
		else if(fabs(p1 - 1) < eps)
		{
			puts("1.00000");
			continue;
		}
		p14 = p4 / (1 - p1);
		p13 = p3 / (1 - p1);
		p12[0] = 1;
		for(int i = 1;i < MAXN;++i)
		p12[i] = p12[i - 1] * (p2 / (1 - p1));
		
		dp[1][1] = p4 / (1 - p1 - p2);
		
// 		for(int i = 1;i <= n;++i)
// 		for(int j = 1;j <= m;++j)	dp[i][j] = -1;
//		/*
		for(int i = 2;i <= n;++i)
		{
			double sum = 0;
			for(int j = 1;j <= i;++j)
			if(j == 1)
			c[j] = p14;
			else
			c[j] = (dp[i - 1][j - 1] * p13 + (j <= k ? p14 : 0));
			
			for(int j = 1;j <= i;++j)	sum += c[j] * p12[i - j];
			dp[i][i] = sum / (1 - p12[i]);
			
			dp[i][1] = dp[i][i] * p12[1] + p14;
			for(int j = 2;j < i;++j)
			dp[i][j] = dp[i][j - 1] * p12[1] + c[j];
		}
//		*/
//		dfs(n,m);
		printf("%.5f\n",dp[n][m]);
	}
	return 0;
}

数位DP练习

手机号码

dp[pos][pre2][pre1][p4][p8][pre];
表示前pos个,前一个数字为pre1,前两个数字为pre2,存在4?,存在8?,存在前导0?的符合条件的答案总数:
AC代码

点击查看代码
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 15;
typedef long long ll;
ll dp[MAXN][MAXN][MAXN][2][2][2];
int stk[MAXN];
/*
11100 11199
1 10
10000000000 
99999999999
*/
ll dfs(int pos,int pre2,int pre1,bool p4,bool p8,bool ok,bool flag)
{
	if(p4 && p8)	return 0;
	if(pos == 0)	return ok;
	if(flag && dp[pos][pre2][pre1][p4][p8][ok] != -1)
		return dp[pos][pre2][pre1][p4][p8][ok];
	
	int Max = flag ? 9 : stk[pos];
	
	ll ans = 0;
	for(int i = 0;i <= Max;++i)
	ans += dfs(pos - 1,pre1,i,p4 || i == 4,p8 || i == 8,\
	ok || (i == pre2 && i == pre1),flag || i != Max);
	
	if(flag)	return dp[pos][pre2][pre1][p4][p8][ok] = ans;
	return ans;
}
ll cal(ll x){
	int pos = 0;
	if(x < 1e10)	return 0;
	while(x)
	{
		stk[++pos] = x % 10;
		x /= 10;
	}
	ll ans = 0;
	for(int i = 1;i <= stk[pos];++i)
	ans += dfs(pos - 1,0,i,i == 4,i == 8,0,i != stk[pos]);
	return ans;
}
int main()
{
	memset(dp,-1,sizeof dp);
	ll l,r;
	scanf("%lld %lld",&l,&r);
	printf("%lld\n",cal(r) - cal(l - 1));
	return 0;
}

明七暗七

好朋友

COUNT数字计数

Balls

柯学送分

抽卡

上面几题较为基础...先偷个懒...

奖励关

一看数据范围,很显然会这样设置状态方程:
dp[i][s]表示前i个宝物,当前已拿取的宝物状态为s所能获得的最大期望分数
对于每个宝物的前置条件也用二进制串进行表示(ss[l],l∈[0,n - 1])
当ss[l] & s == ss[l]表示ss[l]是s的子集,可以拿第l件物品
现在考虑状态的转移:
如果我们从前往后进行转移,如果当前l物品拿的话,枚举之前状态j,转移即为:
dp[i][j | (1 << l)] = dp[i - 1][j] + p[l];
如果不拿的话:
dp[i][j] = dp[i - 1][j];
但是这样很显然,我们无法求得最大的那个策略...
因此我们从后往前进行转移,这样可以从后面那个较优的状态转移过来:
ss[l] & j == ss[l]
dp[i][j] += max(dp[i + 1][j],dp[i + 1][j | (1 << l)] + p[l]) * pp
否则
dp[i][j] += dp[i + 1][j] * pp;
pp是概率...
AC代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 15;
double dp[105][1 << MAXN]; 
int p[MAXN],s[MAXN];
ll f[105][1 << MAXN];
pair<int,int> pre[105][1 << MAXN];
int main()
{
	int k,n;
	scanf("%d %d",&k,&n);
	
	for(int i = 0;i < n;++i)
	{
		scanf("%d",&p[i]);
		int c = 0;
		scanf("%d",&c);
		int ss = 0;
		while(c != 0)
		{
			ss |= (1 << (c - 1));
			scanf("%d",&c);
		}
		s[i] = ss;
	}
	/*
	memset(f,0xcf,sizeof f);
	f[0][0] = 0;
	pre[0][0] = {-1,0}; 
	for(int i = 1;i <= k;++i)
	{
		for(int j = 0;j < (1 << n);++j)
		{
			for(int l = 0;l < n;++l)
			{
				if((s[l] & j) != s[l])	continue;
				if(f[i][j | (1 << l)] < f[i - 1][j] + p[i])
				{
					f[i][j | (1 << l)] = f[i - 1][j] + p[i];
					pre[i][j | (1 << l)] = {l,j};
				}
			}
		}
	}
	ll Max = -1e9,sta = 0;
	for(int i = 0;i < (1 << n);++i)
	{
		if(Max < f[k][i])
		{
			Max = f[k][i];
			sta = i;
		}
	}
	int now = sta,m = n;
	while(m != 0)
	{
		printf(">>%d\n",pre[m][now].first);
		now = pre[m][now].second;
		m -= 1;
	}
	*/
	double pp = 1.0 / n;
	for(int i = k;i >= 1;--i)
	{
		for(int j = 0;j < (1 << n);++j)
		{
			int cnt = 0;
			for(int l = 0;l < n;++l)
			if(j >> l & 1)	cnt += 1;
			if(cnt > i)	continue;
			
			for(int l = 0;l < n;++l)
			{
				if((s[l] & j) == s[l])
				dp[i][j] += max((dp[i + 1][j | (1 << l)] + p[l]),dp[i + 1][j]) * pp;
				else	
				dp[i][j] += dp[i + 1][j] * pp;
			}
		}
	}
	double ans = dp[1][0];
	printf("%.6lf\n",ans);
	return 0;
}

2022-02-19

牛客动态规划课程习题课例题与练习

5555今天被虐爆,根本不想做题

一起玩音游

由动态规划题的惯性,题目要求什么我们的方程就是什么,设方程如下:
dp[i]:前i次点击,获得游戏分数的期望值,答案就是dp[n]
由于每次点击,会产生一个x^2的贡献,x是当前以i结尾的长度
而若求下一次的长度所产生的贡献就是(x + 1) ^ 2(如果这一位还是O的话
那么拆开,可以发现下一次产生的贡献 = 2 * x + 1,
也就是下一次产生的贡献=上一次长度 * 2 + 1
那么转移就是:
dp[i] = dp[i - 1] + x * 2 + 1;
现在问题来到怎么求x:以i结尾的长度
由于x有很多中情况(连续0,连续1,连续2...),而我们要求的是期望,所以这里的x可以当做期望来求(长度平均值),设len[i]是以i为结尾的期望连续O的长度
len[i] = (len[i - 1] + 1) * p[i]
dp[i] = dp[i - 1] + len[i - 1] * 2 + 1
AC代码:

点击查看代码
#include <iostream> 
using namespace std;
const int MAXN = 1e5 + 7;
double p[MAXN];
double dp[MAXN],len[MAXN];//期望分数、期望长度 
int main()
{
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)	scanf("%lf",&p[i]);
	
	for(int i = 1;i <= n;++i)
	{
		len[i] = (len[i - 1] + 1) * p[i];
		dp[i] = dp[i - 1] + (2 * len[i - 1] + 1) * p[i];
	}
	printf("%.10f\n",dp[n]);
	return 0;
}

由题意可知,进行到n - 1天的时候就只剩下一条鱼了
一看数据范围18,直接上状态压缩了...
设方程dp[i][s]表示第i天的时候,状态为s的概率,
很明显我们已知dp[0][(1 << n) - 1] = 1
最终答案为dp[n - 1][1 << i]为第i条鱼的生存概率(这里最后需要做一个归一化,使其成为概率)
很容易想到转移方程:
dp[i][s] += dp[i - 1][s'] * p[j][s];
其中s | (1 << j) == s'
p[j][s]是s状态中的鱼将j鱼杀死的概率,这个可以事前预处理
AC代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 18;
double a[MAXN][MAXN];
double p[MAXN][1 << MAXN];
double dp[MAXN][1 << MAXN];
vector<int> alls[MAXN];
/*
3
0 1 1
0 0 0.5
0 0.5 0

2
0 0.1
0.9 0
*/ 
int main()
{
	int n;
	scanf("%d",&n);
	for(int i = 0;i < n;++i)
	for(int j = 0;j < n;++j)	scanf("%lf",&a[i][j]);
	
	for(int j = 0;j < n;++j)
	{
		for(int i = 1;i < (1 << n);++i)
		{
			if(i >> j & 1)	continue;
			
			int cnt = 0;
			for(int k = 0;k < n;++k)
			if(i >> k & 1)
			{
				cnt += 1;
				p[j][i] += a[k][j];
			}
			p[j][i] /= cnt;
		}
	}
	
	for(int i = 1;i < (1 << n);++i)
	{
		int cnt = 0;
		for(int j = 0;j < n;++j)
		if(!(i >> j & 1))	cnt += 1;
		alls[cnt].push_back(i);
	}
	
	dp[0][(1 << n) - 1] = 1;
	for(int i = 1;i <= n - 1;++i)
	{//枚举天数 
		for(int j = 0;j < alls[i].size();++j)
		{
			int s = alls[i][j],cnt = 0;
			for(int k = 0;k < n;++k)
			{
				if(!(s >> k & 1))
				{//枚举被杀的鱼 
					cnt += 1;
					dp[i][s] += dp[i - 1][s | (1 << k)] * p[k][s];
				}
			}
// 			dp[i][s] /= cnt;
			
//			printf("%d %d -> %d %lf\n",i,s,cnt,dp[i][s]);
		}
	}
	
//	printf("%lf\n",dp[n][0]);
	double sum = 0;
	for(int i = 0;i < n;++i)	sum += dp[n - 1][1 << i];
	
	for(int i = 0;i < n;++i)
	printf("%.10f%c",dp[n - 1][1 << i] / sum," \n"[i == n - 1]);
	return 0;
}

刷野

对于一个怪的,会出现左右两个影响因子,如果我们简单进行线性递推,会发现会缺少一边的影响,因此我们需要通过区间来进行递推,这样可以同时考虑左右对答案的影响
设dp[i][j]是杀死i ~ j范围内的所有怪所需要的伤害
转移即为
dp[i][j] = min(dp[i][j],dp[i][k - 1] + a[k] + dp[k + 1][j] + b[i - 1] + b[j + 1]);
当k = i和k = j的时候需要特殊处理一下,因为这时候不存在dp[i][k - 1]或dp[k + 1][j]
AC代码:

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 505;
int f[MAXN][MAXN];
int a[MAXN],b[MAXN];
signed main()
{
	int n;
	scanf("%lld",&n);
	for(int i = 1;i <= n;++i)	scanf("%lld",&a[i]);
	for(int i = 1;i <= n;++i)	scanf("%lld",&b[i]);
	
	memset(f,0x3f,sizeof f);
	for(int i = 1;i <= n;++i)	f[i][i] = a[i] + b[i - 1] + b[i + 1];
	
	for(int l = 2;l <= n;++l)
	{
		for(int i = 1;i + l - 1 <= n;++i)
		{
			int j = i + l - 1;
			f[i][j] = min(f[i][j],f[i + 1][j] + b[j + 1] + a[i] + b[i - 1]);
			f[i][j] = min(f[i][j],f[i][j - 1] + b[j + 1] + a[j] + b[i - 1]);
			for(int k = i + 1;k < j;++k)
			{//枚举余下的 
				f[i][j] = min(f[i][j],f[i][k - 1] + f[k + 1][j] + a[k] + b[i - 1] + b[j + 1]);
			}
		}
	}
	int ans = 1e9;
	ans = f[1][n];
	printf("%lld\n",ans);
	return 0;
}

[HAOI2011]PROBLEM A

将题目转化一下,一个人前面有a[i]个,后边有b[i]个,那么这个人的rank区间就在
[a[i] + 1,n - b[i]]
再稍微想想,好像如果两个人的这个区间重合了(不完全),那么一定有一个人说的是假话。
如果是完全重合的话,那么这两个人是相同的分数,可以是没有人说假话(在区间个数允许的情况下)。
所以问题就转化为对于[1,n]的区间,求出区间中可以放置上诉n个区间的最多个数(题目求的是不能放置的最少个数)。
设dp[i]为当前为rank为i可以放置区间的最多个数
dp[i] = max(dp[i],dp[j - 1] + min(i - j + 1,[i][j]区间个数));
其中j <= i,由于题目所给的区间我们排序后具有单调性质,那么可以在O(n)复杂度下完成求解~
AC代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 7;
int dp[MAXN];
/*
6
0 3
1 3
2 3
3 2
3 1
4 0

6
0 5
1 4
2 3
3 2
4 1
5 0

3
0 2
1 0
1 0
*/
map<pair<int,int>,int> mp;
vector<int> g[MAXN];
bool cmp2(int a,int b)
{return a > b;}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		if(x + 1 > n - y)	continue;
		if(mp.count({x + 1,n - y}))	{
			mp[{x + 1,n - y}] += 1;
			continue;
		}
		
		mp[{x + 1,n - y}] += 1;
		if(n - y >= 1 && n - y <= n)
		g[n - y].push_back(x + 1);
	}
	
	for(int i = 1;i <= n;++i)
	{
		dp[i] = dp[i - 1];
		for(int j = 0;j < g[i].size();++j)
		{
			int x = g[i][j];
			if(x - 1 >= 0)
			dp[i] = max(dp[i],dp[x - 1] + min(i - x + 1,mp[{x,i}]));
		}
	}
	printf("%d\n",n - dp[n]);
	return 0;
}

灯谜

按照雨巨的描述,这个E[x ^ 3] * (2 ^ m)可以转化为sum(x ^ 3)
因为E[x ^ 3]是期望,期望就是对2m这么多种情况下的平均x3
既然已经有了2 ^ m,当然就只要求在2^m这么多种情况下x ^ 3是多少即可
观察x^3这个东西,我们设x = (x1 + x2 + x3 + ... + xn)
xi是bool类型,1代表第i个灯泡是亮的,否则就是灭的
可以看见,当xi中有3个为1的值就可以对答案sum(x^3)产生1的贡献,其中xi可以重复(如只亮了一个灯泡x1,也是可以产生1的贡献的)
所以我们枚举三个亮着的灯泡(xi,xj,xk),然后设如下方程:
dp[q][s]表示前q个开关,灯泡xi,xj,xk的状态为s的方案数
转移:
dp[q][s] += dp[q - 1][s]:第q个按钮不按
dp[q][s] += dp[q - 1][s']:第q个按钮按
s'就按照按下按钮后,是否会对xi,xj,xk造成改变进行转移就好了
最后对所有的(xi,xj,xk)下所产生的dp[m][7]求一个和就是答案...
AC代码:

点击查看代码
#include <iostream>
using namespace std;
const int MAXN = 55;
typedef long long ll;
const int MOD = 1e9 + 7;
ll dp[MAXN][8];
bool vis[MAXN][MAXN];
int n,m;
int main() {
	scanf("%d %d",&n,&m);

	for(int i = 1; i <= m; ++i) {
		int k;
		scanf("%d",&k);
		while(k--) {
			int x;
			scanf("%d",&x);
			vis[i][x] = 1;
		}
	}

	ll ans = 0;
	for(int k1 = 1; k1 <= n; ++k1)
	for(int k2 = 1; k2 <= n; ++k2)
	for(int k3 = 1; k3 <= n; ++k3)
	{
		dp[0][0] = 1;
		for(int i = 1; i <= m; ++i) 
		{
			for(int j = 0; j < 8; ++j) 
			{
				dp[i][j] = 0;
				dp[i][j] += dp[i - 1][j];
				dp[i][j] %= MOD;
				int nows = j;
				if(vis[i][k1])	nows ^= 1;
				if(vis[i][k2])	nows ^= 2;
				if(vis[i][k3])	nows ^= 4;
				dp[i][j] += dp[i - 1][nows];
				dp[i][j] %= MOD;
			}
		}
		ans += dp[m][7];
		ans %= MOD;
	}
	printf("%lld\n",ans);
	return 0;
}
???突然发现,3月5号就是PAT顶级考试了???三月三就回学校了,没几天了?还没开始动仿真题??,开始害怕

2022-02-20

今天清一清PAT顶级题库...

1028 Do All Roads Lead to Rome

Orz还没搞懂,先挖个坑吧

1030 Beautiful Subsequence

题意:找到一个串中的所有子序列,使得其中存在相邻的元素相差 <= m
如果直接正向去求的话,相较于先求反方向的。再将此项减去会比较难
那么考虑它的反问题:找到子序列的个数,使该子序列的元素相差 > m
那么答案就是子序列总个数 - 它的反问题↑
对于这个反问题,由于是找子序列,那么我们可以考虑怎么动态规划
设dp[i]表示以a[i]为结尾的满足上诉条件的子序列个数
那么dp[i] = sum(dp[j]) + 1
其中abs(a[j] - a[i]) > m
考虑一下怎么优化,可以看到找到a[j]指的是和a[i]相差大于m的,所以我们只需要统计在[1,a[i] - m - 1]和[a[i] + m + 1,N]的所有答案即可
很显然使用线段树或者树状数组就行
AC代码:

点击查看代码
#include <iostream>
#define lowbit(x) (x & (-x))
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 2e5 + 7;
int a[MAXN];
ll c[MAXN << 1],dp[MAXN];
int n,m;
const int N = 2e5 + 7;
void add(int x,ll y)
{
	while(x <= N)
	{
		c[x] += y;
		c[x] %= MOD;
		x += lowbit(x);
	}
}
ll ask(int x)
{
	ll ans = 0;
	if(x < 0)	return 0;
	while(x)
	{
		ans += c[x];
		ans %= MOD;
		x -= lowbit(x);
	}return ans;
}
ll ksm(ll a,ll b)
{
	ll t = 1;
	while(b)
	{
		if(b & 1)	t = t * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}return t;
}
/*
4 2
5 8 11 14

2 1
-100 -99
*/
int main()
{
	scanf("%d %d",&n,&m);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]),a[i] += 1e5;
	
	for(int i = 1;i <= n;++i)
	{
		ll x = ask(a[i] - m - 1);
		ll y = ask(N) - ask(min(a[i] + m,N));
		y = (y % MOD + MOD) % MOD;
		x = (x + y) % MOD;
		
		dp[i] = (x + 1)%MOD;
		add(a[i],dp[i]);
	}
	ll ans = 0;
	for(int i = 1;i <= n;++i)	ans += dp[i],ans %= MOD;
//	printf("%lld\n",ans);
//	ans = (ans % MOD + MOD) % MOD;
	ll sum = ksm(2,n) - 1;
//	sum -= (n + 1);
//	sum = (sum % MOD + MOD) % MOD;
    sum %= MOD;
	printf("%lld\n",((sum - ans) % MOD + MOD) % MOD);
	return 0;
}
### 1022 Werewolf 暴力+简单剪枝即可... AC代码:
点击查看代码
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int MAXN = 1e2 + 7;
int a[MAXN],b[MAXN];
int ok[MAXN];
bool ly[MAXN];
int n,m,l;
vector<int> ans,tmp;
void dfs(int i,int liar,int wolf)
{
	if(liar > l)	return ;
	if(wolf > m)	return ;
	if(liar + n - i + 1 < l)	return ;
	if(i == n + 1)
	{
		if(liar != l)	return ;
		
		int left = 0;
		vector<int> undef;
		for(int i = 1;i <= n;++i)	if(ok[i] == -1)	{
			left += 1;
			undef.push_back(i);
		}
		
//		if(ly[1] == 1 && ly[5] == 1 && ly[6] == 1)
//		{
//			for(int i = 1;i <= n;++i)
//			printf("%d%c",ok[i]," \n"[i == n]);
//			for(int i = 1;i <= n;++i)
//			printf("%d%c",ly[i]," \n"[i == n]);
//		}
		if(wolf < m && wolf + left < m)	return ;
		
		for(int i = n;i >= 1;--i)
		{
			if(ok[i] == -1)
			{
				if(wolf < m)
				ok[i] = 0,wolf += 1;
				else
				ok[i] = 1;
			}
		}
//		if(ok[6] == 0 && ok[5] == 0)
//		{
//			for(int i = 1;i <= n;++i)
//			printf("%d%c",ly[i]," \n"[i == n]);
//			for(int i = 1;i <= n;++i)
//			printf("%d%c",ok[i]," \n"[i == n]);
//		}
		
		int cnt = 0;
		vector<int> now;
		for(int i = n;i >= 1;--i)
		if(ok[i] == 0)
		cnt += ly[i],now.push_back(i);
		
		for(int i = 0;i < undef.size();++i)	ok[undef[i]] = -1;
		
		if(cnt == m || cnt == 0)	return ;
		
		if(ans.size() == 0)	ans = now;
		else	ans = max(ans,now);
		return ;
	}
	
	int x = abs(a[i]);
	if(ok[x] != -1)
	{
		if(ok[x] == (a[i] > 0))
		{
			ly[i] = 0;
			dfs(i + 1,liar,wolf);
		}
		else//说谎了 
		{
			ly[i] = 1;
			dfs(i + 1,liar + 1,wolf);
		}
	}
	else
	{
		ok[x] = (a[i] > 0);
		ly[i] = 0;
		
		dfs(i + 1,liar,wolf + (ok[x] == 0));
		
		ok[x] ^= 1;
		ly[i] = 1;
		dfs(i + 1,liar + 1,wolf + (ok[x] == 0));
		
		ok[x] = -1;
	}
}
/*
3 2 2
+2
+3
-2
*/
int main()
{
	memset(ok,-1,sizeof ok);
	scanf("%d %d %d",&n,&m,&l);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	
	dfs(1,0,0);
	if(ans.size() == 0)
	puts("No Solution");
	else
	{
		for(int i = 0;i < m;++i)
		printf("%d%c",ans[i]," \n"[i == m - 1]);
	}
	return 0;
}
posted @ 2022-02-19 01:14  K0njac  阅读(65)  评论(0编辑  收藏  举报