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;
}
View Code

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
",dp[0][0]);
    }
    return 0;
}
View Code

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
", min(dp[n][0], min(dp[n][1], dp[n][2])));
}
View Code

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
", dp_f[n + 1], dp_z[n + 1]);
    return 0;
}
View Code

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
");
        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
",ans);
    return 0;
}
View Code

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
",ans);
    return 0;
}
View Code

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
", ans);
 
    return 0;
}
View Code

约瑟夫环变形问题
首先先明确约瑟夫问题,已知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
", (dp[n - 1] + m) % n + 1);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/fengzhongzhuifeng/p/12201642.html