2017 UESTC Training for Dynamic Programming

Posted 掉血菜鸡煮熟中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2017 UESTC Training for Dynamic Programming相关的知识,希望对你有一定的参考价值。

2017 UESTC Training for Dynamic Programming

A    思维, 或 dp, 很有意思

方法1: 

构造法:蛇形安排赛程表
算法复杂度:O(N^2)
将1-N排成两竖列,每一轮同一行的为对手
保持1的位置不变,其他位置按顺(逆)时方向依次旋转
1    6          1    2          1    3          1    4          1    5      
2    5          3    6          4    2          5    3          6    4      
3    4          4    5          5    6          6    2          2    3

1             N
2           N-1
3           N-2
.              .
.              .
.              .
N/2    N/2+1

技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 200005;

int n, a[N];
int main()
{
    scanf("%d", &n);
    rep(i,2,n) a[i]=i;
    rep(i,n+1,2*n-1) a[i]=a[i-(n-1)];
    rep(i,2*n,3*n-2) a[i]=a[i-(n-1)];
    rep(i,n+1,2*n-1)
    {
        printf("1 %d ", a[i]);
        rep(ca,1,(n-2)/2)
        {
            printf("%d %d ", a[i-ca], a[i+ca]);
        }
        puts("");
    }

    return 0;
}
View Code

方法2:

 DP做法
算法复杂度:O(N^3)
如果我们知道4支队伍的赛程表,就可以推出8支队伍的赛程表
1-2 3-4
1-3 2-4
1-4 2-3
第一部分:将8支队伍分成两组进行组内对抗
1-2 3-4        5-6 7-8
1-3 2-4        5-7 6-8
1-4 2-3        5-8 6-7
第二部分:两组队伍进行组间对抗
1-5 2-6 3-7 4-8
1-6 2-7 3-8 4-5
1-7 2-8 3-5 4-6
1-8 2-5 3-6 4-7
所以,我们如果能知道N(N为偶数)支球队的赛程表,就可以推出2*N支球队的赛程表
如果N为奇数,N支球队的赛程表和2*N支球队的赛程表该怎么推呢?
N为奇数的话,赛程表也得进行N轮,每轮会有一支球队轮空
比如说N=3(可假想N=4,多一个对手0,与0对阵算作轮空)
第一轮:1-2    3(轮空)
第二轮:1-3    2(轮空)
第三轮:2-3    1(轮空)
我们知道了三支球队的赛程表,六支球队的赛程表该这么转移
6支球队分成两组 先进行组内对抗
1-2     3(轮空)    4-5     6(轮空)
1-3     2(轮空)    4-6     5(轮空)
2-3     1(轮空)    5-6     4(轮空)
但是,我们不允许两支球队轮空,就得把分别在组内对抗按道理应轮空的两支球队对阵
1-2 4-5 3-6
1-3 4-6 2-5
2-3 5-6 1-4
再进行组间对抗
1-4 2-5 3-6    (这里的对阵在组内对抗进行过了,应去掉)
1-5 2-6 3-4
1-6 2-4 3-5

dp解法待补

D    01、完全 混合背包   可做板子

技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 20005;

int t[N], a[N], b[N];

int n, volume, dp[N];
//memset(dp, t, sizeof(dp));  dp[0] = 0;  //恰好装满:t=128,最大:t=0
void ZeroonePack(int wi, int vi)
{
    for(int i=volume; i>=vi; --i)
        dp[i] = max(dp[i], dp[i-vi]+wi);
}
void CompletePack(int wi, int vi)
{
    for(int i=vi; i<=volume; ++i)
        dp[i] = max(dp[i], dp[i-vi]+wi);
}
void MultiplePack(int wi, int vi, int mi)
{
    if(mi*vi>volume) { CompletePack(wi, vi); return ; }
    for(int i=1; i<mi; mi-=i, i<<=1) ZeroonePack(wi*i, vi*i);
    ZeroonePack(wi*mi, vi*mi);
}

int main()
{
    scanf("%d %d", &n, &volume);
    rep(i,1,n) scanf("%d %d %d", &t[i], &a[i], &b[i]);
    mes(dp, 0);
    rep(i,1,n)
    {
        if(t[i]==1) CompletePack(a[i], b[i]);
        else ZeroonePack(a[i], b[i]);
        //MultiplePack(a[i], b[i], t[i]==1 ? 1e4+10 : 1);
    }
    printf("%d\n", dp[volume]);

    return 0;
}
View Code

F      递推dp,水题

技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 1005;

int n, m, a[N][N], dp[N][N];
bool check(int x, int y)
{
    return (x>0 && y>0);
}
int main()
{
    scanf("%d %d", &n, &m);
    rep(i,1,n) rep(j,1,m)
        scanf("%d", &a[i][j]), dp[i][j]=-INF;
    dp[1][1]=a[1][1];
    int ans=-INF;
    rep(i,1,n) rep(j,1,m)
    {
        if(check(i-1, j) && dp[i-1][j]>0) dp[i][j]=max(dp[i][j], dp[i-1][j]+a[i][j]);
        if(check(i, j-1) && dp[i][j-1]>0) dp[i][j]=max(dp[i][j], dp[i][j-1]+a[i][j]);
        if(check(i-1, j-2) && dp[i-1][j-2]>0) dp[i][j]=max(dp[i][j], dp[i-1][j-2]+a[i][j]);
        if(check(i-2, j-1) && dp[i-2][j-1]>0) dp[i][j]=max(dp[i][j], dp[i-2][j-1]+a[i][j]);
        ans=max(dp[i][j], ans);
    }
    printf("%d\n", ans);

    return 0;
}
View Code

G     最长上升子序列,水,要打印路径,只会O(n^2)的。。

技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 200005;

int n, a[N], dp[N], path[N], ans[N];
int main()
{
    int T;  scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        rep(i,1,n) scanf("%d", &a[i]);
        rep(i,1,n)
        {
            dp[i]=1, path[i]=0;
            rep(j,1,i-1) if(a[j]<a[i])
            {
                if(dp[i] <= dp[j]+1) {
                    dp[i]=dp[j]+1;
                    path[i]=j;
                }
            }
        }
        int now=1, len=1;
        rep(i,1,n)
            if(len<dp[i]) len=dp[i], now=i;
            else if(len==dp[i] && a[now]>a[i]) now=i;
        printf("%d ", len);
        int k=0;
        while(now!=0) ans[++k]=a[now], now=path[now];
        per(i,k,1) printf("%d ",ans[i]);
        puts("");
    }

    return 0;
}
View Code

L    轮廓线动态规划(插头dp)

题意:给定一个n*m的矩阵,使用1*2的小长方形覆盖矩阵,要求完全覆盖的同时不出现重合,也不允许超出边界,问有多少种可能的覆盖方法,方案数对1e9+7取模 ( 2<=n<=1000, 3<=m<=5 )。

tags: 好难,看了题解。。

方法1 : 传统方法,以整行整列为状态。

x,y表示当前需要考虑放小长方形的位置,st1,st2分别是当前行和下一行的情况,dp[x][y][st1][st2]表示当前要考虑(x,y),当前行状态是st1,下一行状态是st2,可以有多少种合法的放置方法。 然后类似于记忆化dp。

状态转移:

if(y+1<=m && !(st1 & (1<<y)) && !(st1 & 1<<(y+1))) //横放,那么(x,y),(x,y+1)两个位置不能已经被覆盖
{
    res=(res+DP(x,y+2,(st1|(1<<(y)|(1<<(y+1)))),st2))%MOD; //如果状态转移合法,接下来就考虑(x,y+2),并且更新st1
}
if(x+1<=n && !(st1 & (1<<y))) //同上,竖放
{
    res=(res+DP(x,y+1,(st1|(1<<y)),(st2|(1<<y))))%MOD; 
}
技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 1005, mod = 1e9+7;

int n, m;
ll  dp[N][7][1<<6][1<<6];
inline ll  DP(int x, int y, int lin1, int lin2)
{
    if(x==n+1)  return 1;
    if(y==m+1)  return DP(x+1, 1, lin2, 0);
    if(dp[x][y][lin1][lin2]) return dp[x][y][lin1][lin2];
    if((lin1>>(y-1))&1) return DP(x, y+1, lin1, lin2);
    ll  res=0;
    if(y+1<=m && !((lin1>>(y-1))&1) && !((lin1>>(y))&1) )
    {
        res += DP(x, y+2, (lin1|(1<<(y-1))|(1<<(y))) , lin2);
        res %= mod;
    }
    if(x+1<=n && !((lin1>>(y-1))&1) && !((lin2>>(y-1))&1) )
    {
        res += DP(x, y+1, (lin1|(1<<(y-1))), (lin2|(1<<(y-1))) );
        res %= mod;
    }
    return dp[x][y][lin1][lin2]=res;
}
int main()
{
    scanf("%d %d", &n, &m);
    if(n*m&1) puts("0");
    else printf("%lld\n", (DP(1, 1, 0, 0)+mod)%mod);

    return 0;
}
View Code

方法2 : 插头dp,利用 “窄” 的特点,把参差不齐的 “轮廓线” 作为状态的一部分。

参考大白书 384页

伪代码(模板)

int dp[2][N], cur = 0;
所有d[0][j]初始化为1;
从小到大枚举每个要算的阶段
{
    cul ^= 1;
    所有dp[cul][j]初始化为0;  //只能在这里这样做,因为现在dp[cul]存着以前某阶段的值
    for 上个阶段的每个结点j
        for j的每个后继结点k
            d[cul][k] += d[1-cul][j];
}
技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 1005, mod = 1e9+7;

int n, m, cur;
ll  dp[2][N];
void update(int a, int b)
{
    if(b&(1<<m)) dp[cur][b^(1<<m)] += dp[cur^1][a]%mod;
}
int main()
{
    scanf("%d %d", &n, &m);
    cur=0;
    rep(i,0,N-1) dp[cur][i]=1;
    rep(i,1,n) rep(j,1,m)  //枚举当前要算的阶段
    {
        cur^=1;
        mes(dp[cur], 0);
        for(int k=0; k<(1<<m); ++k)   //枚举上个阶段的状态
        {
            update(k, k<<1);    //不放
            if(i>1 && !(k&(1<<m-1))) update(k, (k<<1)|(1<<m)|1); //向上放
            if(j>1 && !(k&1)) update(k, (k<<1)|3); //向左放
        }
    }
    printf("%lld\n", (dp[cur][(1<<m)-1]+mod)%mod);

    return 0;
}
View Code

M    多重背包,和D题差不多

N    多位费用完全背包

题意:已知 n个参赛队员,对于第 i个队员,每写一行代码,就会留下 ai个bug。最后一题需要写 m行代码,请安排各个队员写的代码行数(显然要非负),使得整个代码的bug数不超过 b个。然后,在ACM玄学之神的保佑下,这份不超过b个bug的代码就能AC了。问你有多少种不同的安排方案可以写出一份AC代码,要求方案数对mod取模。

tags:

1、dp[i][j][k],已经考虑了前i个队员,写了j行代码,存在k个Bug的方案数,通过递推顺序,第一维可以直接省略。
2、if (k>=arr[i])   dp[j][k]=(dp[j][k]+dp[j-1][k-arr[i]])%mod;    // 实际含义是dp[i][j][k]=(dp[i-1][j][k]+dp[i][j-1][k-arr[i]]), 这里的递推顺序实际上是一个无穷背包的思想

技术分享
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;++i)
#define per(i,b,a) for (int i=b;i>=a;--i)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi  first
#define se  second
typedef long long ll;
const int N = 505;

int n, m, b, mod, a[N];
ll   dp[N][N];
void CompletePack(int ai, int vi)
{
    rep(i,ai,b)  rep(j,vi,m)
    {
        dp[i][j] += dp[i-ai][j-vi];
        dp[i][j] %= mod;
    }
}
int main()
{
    scanf("%d %d %d %d", &n, &m, &b, &mod);
    rep(i,1,n)  scanf("%d", &a[i]);
    dp[0][0]=1;
    rep(i,1,n)
    {
        CompletePack(a[i], 1);
    }
    ll  ans=0;
    rep(i,0,b)
        ans = (ans+dp[i][m])%mod;
    printf("%lld\n", ans);

    return 0;
}
View Code

以上是关于2017 UESTC Training for Dynamic Programming的主要内容,如果未能解决你的问题,请参考以下文章

2017 UESTC Training for Data Structures

2017 UESTC Training for Dynamic Programming

2017 UESTC Training for Graph Theory

2017 UESTC Training for Dynamic Programming

2017 UESTC Training for Graph Theory

2018 UESTC Training for Search Algorithm & String