2020 HZNU Winter Training Day 4
POJ 2279
对于题目,我们将学生按高度从高到矮安排,那么这样的话其实在每一行的层面肯定是可以保证左边比右边高的,因为前面安排的都是更高的学生。
安排新的学生时,对于每一行有如下考虑:
1.该行未满
2.行号为1,或者该行的学生数比上一行少(这样的话,就能保证如果新插入学生,那么这个学生的高度也比上一行同一位置的学生高度低)
f[a1][a2][a3][a4][a5]表示每一行学生数
边界条件:f[0][0][0][0][0]=1;
坑点:MLE,定义dp数组的时候要根据输入的行号来定义,不然会MLE
#include<iostream> #include<cstring> #include<cmath> #include<queue> #include<stack> #include<list> #include<map> #include<set> #include<sstream> #include<string> #include<vector> #include<cstdio> #include<ctime> #include<bitset> #include<algorithm> #include<string.h> using namespace std; typedef long long ll; #define lson l , mid , rt << 1 #define rson mid + 1 , r , rt << 1 | 1 int read() { int ans = 0; int flag = 1; char ch = getchar(); while ((ch > '9' || ch < '0') && ch != '-') ch = getchar(); if (ch == '-') flag = -1, ch = getchar(); while (ch >= '0' && ch <= '9') ans = ans * 10 + ch - '0', ch = getchar(); return ans * flag; } int n[6]; int main() { int k; while (~scanf("%d",&k)) { if (k == 0) break; for (int i = 0; i < 6; i++){ n[i] = 0; } for (int i = 1; i <= k; i++){ cin >> n[i]; } ll dp[n[1] + 1][n[2] + 1][n[3] + 1][n[4] + 1][n[5] + 1]; memset(dp, 0, sizeof dp); dp[0][0][0][0][0] = 1;//每维均为0时安排种类数为1。 for (int a1 = 0; a1 <= n[1]; a1++) { for (int a2 = 0; a2 <= n[2]; a2++) { for (int a3 = 0; a3 <= n[3]; a3++) { for (int a4 = 0; a4 <= n[4]; a4++) { for (int a5 = 0; a5 <= n[5]; a5++) { if (a1 < n[1]) dp[a1 + 1][a2][a3][a4][a5] += dp[a1][a2][a3][a4][a5];//如果第一维小于n[1],就还可以往第一排安排人 if (a1 > a2 && a2 < n[2]) dp[a1][a2 + 1][a3][a4][a5] += dp[a1][a2][a3][a4][a5];//如果第一维人数大于第二维人数并且第二维人数小于n[2],就可以在第二排安排人 if (a1 > a3 && a2 > a3 && a3 < n[3]) dp[a1][a2][a3 + 1][a4][a5] += dp[a1][a2][a3][a4][a5]; if (a1 > a4 && a2 > a4 && a3 > a4 && a4 < n[4]) dp[a1][a2][a3][a4 + 1][a5] += dp[a1][a2][a3][a4][a5]; if (a1 > a5 && a2 > a5 && a3 > a5 && a4 > a5 && a5 < n[5]) dp[a1][a2][a3][a4][a5 + 1] += dp[a1][a2][a3][a4][a5]; } } } } } cout << dp[n[1]][n[2]][n[3]][n[4]][n[5]] << endl; } return 0; }
POJ 2096 概率DP writed by kuangbin
题意:一个软件有s个子系统,会产生n种bug。某人一天会发现一个bug,这个bug属于一个子系统,属于一个分类,每个bug属于某个子系统的概率是1/s,属于某种分类的概率是1/n。问发现n种bug,每个子系统都发现bug的天数的期望。
dp[i][j]表示已经发现i种bug,j个系统的bug,达到目标还需要的天数的期望
dp[n][s]=0,求解dp[0][0]的值
dp[i][j]的状态转化:
dp[i][j],发现一个bug属于已有的i个分类和j个系统。概率为(i/n)*(j/s)
dp[i][j+1],发现一个bug属于已有的i个分类,不属于已有的j个系统。概率为(i/n)*(1-j/s)
dp[i+1][j],发现一个bug不属于已有的i个分类,属于已有的j个系统。概率为(1-i/n)*(j/s)
dp[i+1][j+1],发现一个bug不属于已有的i个分类和已有的j个系统。概率为(1-i/n)*(1-j/s);
方程可以整理为:dp[i][j]=(i/n)*(j/s)*dp[i][j]+(i/n)*(1-j/s)*dp[i][j+1]+(1-i/n)*(j/s)*dp[i+1][j]+(1-i/n)*(1-j/s)*dp[i+1][j+1]+1
解得: dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j);
#include<cstdio> #include<cmath> #include<iostream> #include<algorithm> #include<cstring> #include<string> #include<set> #include<vector> #include<map> #include<queue> using namespace std; typedef long long ll; typedef pair<int,int> pr; const int maxn=1e6+9; const int N=1<<20; const int inf=0x3f3f3f3f; const ll INF=0x3f3f3f3f3f3f3f3f; const ll mod=1e9+7; const int temp=233; const double eps=0.0000001; const double PI=acos(-1); const int dx[] = {0,0,1,1,1,-1,-1,-1}; const int dy[] = {-1,1,1,-1,0,1,-1,0}; inline int read(){ int num=0, w=0;char ch=0; while (!isdigit(ch)) {w|=ch=='-';ch = getchar();} while (isdigit(ch)) {num = (num<<3) + (num<<1) + (ch^48);ch = getchar();}return w? -num: num; } int n,s; double dp[1010][1010]; int main(){ while(~scanf("%d%d",&n,&s)){ dp[n][s]=0; for(int i=n;i>=0;i--) for(int j=s;j>=0;j--) { if(i==n&&j==s)continue; dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j); } printf("%.4f\n",dp[0][0]); } return 0; }
C题:
dp[ i ][ 0 ] 就代表, 你的 第一个人取 1 ~ i 的花费。
dp[ i ][ 1 ] 就表示, 第一个人 拿了 1 ~ i 的前缀 1 ~ pos 部分, 第二个人拿了, 剩下的 pos ~ i 部分的最少花费。
d[i][2]表示,第二个人拿完到pos后,最后一人拿最后剩下的到i。所要用的最小花费。
状态转移方程为:
dp[i][0] = dp[i - 1][0] + (A[i] != 0 ? 1 : 0);
dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + (A[i] != 1 ? 1 : 0);
dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2])) + (A[i] != 2 ? 1 : 0);
#include<stdio.h> #include<algorithm> #define LL long long using namespace std; const int MAXN = 2e5 + 15; int f[MAXN]; int dp[MAXN][3]; int main() { int a, b, c; scanf("%d %d %d", &a, &b, &c); int n = a + b + c; for(int i = 1; i <= a; i++) { int x; scanf("%d", &x); f[x] = 0; } for(int i = 1; i <= b; i++) { int x; scanf("%d", &x); f[x] = 1; } for(int i = 1; i <= c; i++) { int x; scanf("%d", &x); f[x] = 2; } for(int i = 1; i <= n; i++) { dp[i][0] = dp[i - 1][0] + (f[i] != 0 ? 1 : 0); dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + (f[i] != 1 ? 1 : 0); dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2])) + (f[i] != 2 ? 1 : 0); } printf("%d\n", min(dp[n][0], min(dp[n][1], dp[n][2]))); }
D题:
题解:创建两个dp数组,dp_z[i]和dp_f[i],表示和元素a[i]之前和a[i]连接的的所有正区间和负区间个数。
转移方程为:
if (a[i] > 0){
dp_z[i] = dp_z[i - 1] * 2 - dp_z[i - 2] + 1;
dp_f[i] = dp_f[i - 1] * 2 - dp_f[i - 2];
}
else if (a[i] < 0){
dp_z[i] = dp_f[i - 1] + dp_z[i - 1] - dp_f[i - 2];
dp_f[i] = dp_f[i - 1] + dp_z[i - 1] - dp_z[i - 2] + 1;
}
else {
dp_z[i] = dp_z[i - 1];
dp_f[i] = dp_f[i - 1];
}
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; typedef long long ll; const int MAXN = 2e5 + 15; int n; int main(){ ll a[MAXN], dp_z[MAXN]={0}, dp_f[MAXN]={0}; scanf("%d", &n); for (int i = 2; i <= n + 1; i++) scanf("%lld", &a[i]); for (int i = 2; i <= n + 1; i++){ if (a[i] > 0){ dp_z[i] = dp_z[i - 1] * 2 - dp_z[i - 2] + 1; dp_f[i] = dp_f[i - 1] * 2 - dp_f[i - 2]; } else if (a[i] < 0){ dp_z[i] = dp_f[i - 1] + dp_z[i - 1] - dp_f[i - 2]; dp_f[i] = dp_f[i - 1] + dp_z[i - 1] - dp_z[i - 2] + 1; } else { dp_z[i] = dp_z[i - 1]; dp_f[i] = dp_f[i - 1]; } } printf("%lld %lld\n", dp_f[n + 1], dp_z[n + 1]); return 0; }
codeforces629C Famil Door and Brackets
题意:给你一个长度为m的括号匹配串s(不一定恰好匹配),让你在这个串的前面和后面加上一些括号匹配串,使得这个括号串平衡,即p+s+q达到平衡(平衡的含义是对于任意位置的括号前缀和大于等于0,且最后的前缀和为0)(前缀和为'('的数量-')'的数量)。最后串的长度为n。
解法:枚举这个字符窜前面p字符窜的长度,因为总长度为n,所以后面q字符窜的长度就为n-m-p。p字符窜要满足任意位置的前缀和大于等于0,所以保证p的前缀和+s中最小的前缀和minn>=0,p的方案确定了,q的方案也就确定了。我们使用dp[i][j]表示长度为i的字符窜,平衡度为j的方案数,可以先预处理出来dp的值。然后枚举p的长度和平衡度,ans+=dp[p][c]*dp[n-m-p][now+c](now为m串的平衡度),根据()对称性,长度为i,平衡度为-j的dp值也为dp[i][j]
#include<cstdio> #include<cmath> #include<iostream> #include<algorithm> #include<cstring> #include<string> #include<set> #include<vector> #include<map> #include<queue> using namespace std; typedef long long ll; typedef pair<int,int> pr; const int maxn=1e6+9; const int N=1<<20; const int inf=0x3f3f3f3f; const ll INF=0x3f3f3f3f3f3f3f3f; const ll mod=1e9+7; const int temp=233; const double eps=0.0000001; const double PI=acos(-1); const int dx[] = {0,0,1,1,1,-1,-1,-1}; const int dy[] = {-1,1,1,-1,0,1,-1,0}; inline int read(){ int num=0, w=0;char ch=0; while (!isdigit(ch)) {w|=ch=='-';ch = getchar();} while (isdigit(ch)) {num = (num<<3) + (num<<1) + (ch^48);ch = getchar();}return w? -num: num; } int n,m; ll ans; char s[maxn]; ll dp[3000][3000]; int main(){ scanf("%d%d",&n,&m); if(n&1) { printf("0\n"); return 0; } scanf("%s",s); dp[0][0]=1; for(int i=1;i<=n-m;i++){//预处理前缀为i的,(-)值为j的方案数目 for(int j=0;j<=i;j++){ if(j>0) dp[i][j]=(dp[i-1][j+1]+dp[i-1][j-1])%mod; else dp[i][j]=dp[i-1][j+1]; } } int minn=inf;//记录下s中(-)值的最小值 int num=0; for(int i=0;i<m;i++){ if(s[i]=='(') num++; else num--; minn=min(minn,num); } int len=n-m; ans=0; for(int i=0;i<=len;i++){ for(int j=0;j<=i;j++){ if(j+minn<0||j+num>len-i) continue;//前缀p+s括号无法平衡||p+s+q无法平衡 ans=(ans+dp[i][j]*dp[len-i][j+num]%mod)%mod; } } printf("%lld\n",ans); return 0; }
Codeforces580D Kefa and Dishes
题意:菜单上有n道菜,你可以点m样,但是不同点同样的菜。每样菜有它自己的幸福感,然后还加入了k个规则,比如在吃了第i样菜之后,再吃第j样菜,可以获得c的幸福感,问最大的幸福感。
解法:n最大为18,数据一看显然是状压dp,菜的选择状态用二进制来存储。dp[i][j]表示i状态下最后一到菜吃的是第j道菜取得的最大幸福感。若二进制状态下的s的第i位等于1,而第j位等于0,dp[s∣(1<<j)][j]=max(dp[s∣(1<<j)][j],dp[s][i]+a[j]+mp[i][j]); 特殊规则使用邻接矩阵存储。二进制1表示吃,0表示不吃,遍历1<<n种状态,二进制状态下1的个数和=m,则状态合法,更新最大值
#include<cstdio> #include<cmath> #include<iostream> #include<algorithm> #include<cstring> #include<string> #include<set> #include<vector> #include<map> #include<queue> using namespace std; typedef long long ll; typedef pair<int,int> pr; const int maxn=1e6+9; const int N=1<<18; const int inf=0x3f3f3f3f; const ll INF=0x3f3f3f3f3f3f3f3f; const ll mod=1e9+7; const int temp=233; const double eps=0.0000001; const double PI=acos(-1); const int dx[] = {0,0,1,1,1,-1,-1,-1}; const int dy[] = {-1,1,1,-1,0,1,-1,0}; inline int read(){ int num=0, w=0;char ch=0; while (!isdigit(ch)) {w|=ch=='-';ch = getchar();} while (isdigit(ch)) {num = (num<<3) + (num<<1) + (ch^48);ch = getchar();}return w? -num: num; } int n,m,k; int a[maxn]; int mp[100][100]; ll dp[N][20],ans;//dp[i][j]表示状态为i下,最后一个吃的是j的值 int main(){ scanf("%d%d%d",&n,&m,&k); for(int i=0;i<n;i++){ scanf("%d",&a[i]); } int x,y,c; for(int i=0;i<k;i++){ scanf("%d%d%d",&x,&y,&c); mp[x-1][y-1]=c; } for(int i=0;i<n;i++){ dp[1<<i][i]=a[i]; //状态压缩 } int S=1<<n; ans=0; for(int s=0;s<S;s++){ int num=0; for(int i=0;i<n;i++){ if(s&(1<<i)){ num++;//当前状态包括第i个菜时num++ for(int j=0;j<n;j++){//当前状态下最后一道菜吃i时吃第j个菜 if(s&(1<<j)) continue;//j已经被吃则跳过 int new_s=s|(1<<j);//更新吃j后的状态 dp[new_s][j]=max(dp[new_s][j],dp[s][i]+a[j]+mp[i][j]); } } } if(num==m){//吃了m个菜时,更新当前状态下最大值 for(int i=0;i<n;i++){ if(s&(1<<i)) ans=max(ans,dp[s][i]);//当前状态下是否吃了i } } } printf("%lld\n",ans); return 0; }
G题:
题解:考虑dp的做法,dp[i][j]代表以第i个数为右端点,长度求余m的值为j时的最大值。
转移方程:dp[i][j]=dp[i-1][j-1]+ai
dp[i][j]=max(dp[i-1][m-1]+a[i]-k,a[i]-k)(j==1)
dp[i][j]=dp[i-1][m-1] + a[i];
注意:j可表示长度,但会超时,因此想到取模。
#include <cstdio> #include <iostream> using namespace std; typedef long long ll; const int N = 3e5 + 5; ll a[N]; ll dp[N][20]; int main() { int n, m, k; cin >> n >> m >> k; a[0] = 0; for (int i = 1; i <= m; i ++)dp[0][i] = -1e10; ll ans = 0; for (int i = 1; i <= n; i ++){ scanf("%lld", &a[i]); for (int j = 1; j <= m; j ++){ if (j == 1)dp[i][j] = max(a[i] - k, dp[i - 1][m] + a[i] - k); else dp[i][j] = dp[i - 1][j - 1] + a[i]; ans = max(ans, dp[i][j]); } } printf("%lld\n", ans); return 0; }
约瑟夫环变形问题
首先先明确约瑟夫问题,已知n个人围成一圈(编号:1,2,3,…,n),从编号为1的人开始报数,报数为m的那个人出列;从他的下一个人又从1开始数,同样报数为m的人出列;依此循环下去,直到剩余一个人。求最后这一个人在最开始的序列中编号是几号?
以上我们将问题转换为模式相同且规模逐渐缩小的问题,当规模最小即只有一个人n=1时,报数为m-1的人出列,最后出列的人编号为0;当n=2时,报数为m-1的人出列,最后出列人的编号是多少?应该是只有一个人时得到最后出列的序号加上m(因为报数为m-1的人已经出列,剩下那个人才最后出列所以才加上m)
n=1时,f(1)=0;
n=2时,f(2)=[f(1)+m]%2;
n=3时,f(3)=[f(2)+m]%3;
验证结果:2个人围成一圈,数到3的那人出列,求最后那个人的编号?n=2,m=3
f(2)=[f(1)+m]%2=[0+3]%2=1
最后结果加1,则result=2;
正常约瑟夫环是从1号开始,如果从m号,那么就是报数延迟到了m,那最后报数的人也会相应延迟m。
#include<iostream> #include<cstring> #include<cmath> #include<queue> #include<stack> #include<list> #include<map> #include<set> #include<sstream> #include<string> #include<vector> #include<cstdio> #include<ctime> #include<bitset> #include<algorithm> #include<string.h> using namespace std; typedef long long ll; #define lson l , mid , rt << 1 #define rson mid + 1 , r , rt << 1 | 1 int read() { int ans = 0; int flag = 1; char ch = getchar(); while ((ch > '9' || ch < '0') && ch != '-') ch = getchar(); if (ch == '-') flag = -1, ch = getchar(); while (ch >= '0' && ch <= '9') ans = ans * 10 + ch - '0', ch = getchar(); return ans * flag; } const int maxn = 10010; int dp[maxn]; int main() { int n, k, m; while (scanf("%d%d%d", &n, &k, &m) != EOF) { if (n == 0 && k == 0 && m == 0) break; dp[1] = 0; for (int i = 2; i < n; i++) { dp[i] = (dp[i - 1] + k) % i; } printf("%d\n", (dp[n - 1] + m) % n + 1); } return 0; }