1. 程式人生 > >【noip模擬賽 王強的疑惑】 題解

【noip模擬賽 王強的疑惑】 題解

考試題。

是個DP。

50分可以通過子集列舉+線段覆蓋(貪心)完成。

考試沒時間寫了一個子集列舉30分。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 20;
int read() {
    char ch = getchar(); int u = 0, f = 1;
    while (!isdigit(ch)) {if (ch == '-')f = -1; ch = getchar();}
    while (isdigit(ch)) {u = u * 10 + ch - 48; ch = getchar();}return u * f;
}
//int dp[maxn][maxn]...
struct ioi{
    int l, r, num;
    double p;
}b[maxn];
bool cmp(ioi a, ioi b)
{
    if(a.l == b.l) return a.r < b.r;
    return a.l < b.l;
}
int a[maxn], n, q, seq[maxn], last;
double now = 0, ans;
void print_subset(int s, int n)
{
    memset(a, 0, sizeof(a));
    for(int i = 1; i <= n; i++)
    if(s & (1 << (i-1))) a[i] = 1;
}
int main()
{
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);
    n = read(); q = read();
    for(int i = 1; i <= n; i++)
    {
        b[i].l = read(); b[i].r = read(); scanf("%lf",&b[i].p);
        //cin>>b[i].l>>b[i].r>>b[i].p;
        b[i].r += q; b[i].num = i;
    }
    sort(b+1, b+1+n, cmp);
    for(int i = 1; i < (1 << n); i++)
    {
        print_subset(i, n);
        now = 0; last = 0;
        for(int j = 1; j <= n; j++)
        {
            if(a[j] == 1 && b[last].r <= b[j].l)
            {
                now += b[j].p;
                last = j;
            }
        }
        if(now > ans)
        {
            for(int j = 1; j <= n; j++)
            seq[j] = a[j];
            ans = now;
        }
    }
    printf("%0.3lf\n",ans);
    for(int i = 1; i <= n; i++)
    {
        if(seq[i] == 1)
        printf("%d ", b[i].num);
    }
    return 0;
}

注意一點:memset初始化是O(n)的。儘管上次morslin跟我說做試驗memset確實比for一遍快...所以我信了他每次列舉memset了一個1e6的陣列..TLE

考慮正解的DP。

說過狀態設的好,轉移就方便。

一開始設的DP[i][j]表示前i個選了j個的最優..

轉移個錘子。

正解:不妨設DP[i]表示在第i時,可以獲得的最大期望(此期望非彼期望)。

於是只有在第j個結束時間為i才轉移,其他的是DP[i] = DP[i-1]

注意a[j].l這個邊界,要>q才能轉移,會被卡。不寫只有50分。

$ if(a[j].r == i && a[j].l >= q) $

$ DP[i] = max(DP[i], DP[a[j].l-q] + a[j].p) $

這時候得到一個O(MN)的DP。M為最大時間

考慮再優化,如果我們先給每個資訊排一遍序,這樣我們隨著時間i的增大,每個a[j].l和a[j].r也在增大。記錄上次列舉到的j是多少,記為pos,下次直接從j = pos開始。

我們再去列舉每個j的時候就不需要從1開始了,因為每次滿足的先行條件是a[j].r == i。

注意一點,不論這次a[j]有沒有更新DP[i]的值,我們的pos都要改變,否則還是TLE。

code:

#include <stack>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 10;
inline int read() {
    char ch = getchar(); int u = 0, f = 1;
    while (!isdigit(ch)) {if (ch == '-')f = -1; ch = getchar();}
    while (isdigit(ch)) {u = u * 10 + ch - 48; ch = getchar();}return u * f;
}
double dp[maxn];
struct ioi{
    int l, r, num;
    double p;
}a[maxn];
int n, m, q, pos = 1, pre[maxn], ans[maxn], Ans[maxn], cnt;
bool cmp(ioi a, ioi b)
{
    if(a.r != b.r)
    return a.r < b.r;
    else return a.l < b.l;
}
int main()
{
    //freopen("math.in","r",stdin);
    //freopen("math.out","w",stdout);
    scanf("%d%d",&n,&q);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d%lf",&a[i].l,&a[i].r,&a[i].p);
        a[i].num = i;
        m = max(m, a[i].r);
    }
    sort(a+1, a+1+n, cmp);
    
    for(int i = 1; i <= m; i++)
    {
        dp[i] = dp[i-1]; pre[i] = i - 1;
        for(int j = pos; j <= n; j++)
        {
            if(a[j].r > i) break;
            if(a[j].r == i)
            {
                if(a[j].l >= q && dp[i] < dp[a[j].l-q] + a[j].p)
                {
                    dp[i] = dp[a[j].l-q] + a[j].p;
                    ans[i] = a[j].num;
                    pre[i] = a[j].l - q;
                }
                if(a[j].num == 1 && dp[i] < dp[a[j].l-q] + a[j].p)
                {
                    dp[i] = dp[a[j].l-q] + a[j].p;
                    ans[i] = a[j].num;
                    pre[i] = 0;
                }
                pos = j;
            }
        }
    }
    int now = m;
    while(now)
    {
        if(ans[now]) Ans[++cnt] = ans[now];
        now = pre[now];
    }
    printf("%0.3lf\n",dp[m]);
    for(int i = cnt; i >= 1; i--)
    printf("%d ",Ans[i]);
    return 0;
}

輸出路徑的時候是while(now),不是while(pre[now])..

我說怎麼輸出不了最後一個..