1. 程式人生 > >2017 UESTC Training for Dynamic Programming

2017 UESTC Training for Dynamic Programming

輪廓 multi eof complete ons 球隊 tags clu puts

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