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;
}
点击查看代码
#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;
}