@bzoj - [email protected] [POI2015] ?asuchy

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

圆桌上摆放着 n 份食物,围成一圈,第 i 份食物所含热量为 c[i]。

相邻两份食物之间坐着一个人,共有 n 个人。每个人有两种选择,吃自己左边或者右边的食物。如果两个人选择了同一份食物,这两个人会平分这份食物,每人获得一半的热量。

假如某个人改变自己的选择后(其他 n-1 个人的选择不变),可以使自己获得比原先更多的热量,那么这个人会不满意。

请你给每个人指定应该吃哪一份食物,使得所有人都能够满意。

input

第一行一个整数 n (2<=n<=1000000),表示食物的数量(即人数)。食物和人都从1~n编号。

第二行包含 n 个整数 c[1], c[2], …, c[n](1<=c[i]<=10^9)。

假设第 i 个人(1<=i<n)左边是第 i 份食物,右边是第 i+1 份食物;而第 n 个人左边是第 n 份食物,右边是第 1 份食物。

output

如果不存在这样的方案,仅输出一行 NIE。

如果存在这样的方案,输出一行共 n 个整数,第 i 个整数表示第 i 个人选择的食物的编号。如果有多组这样的方案,输出任意一个即可。

sample input

5

5 3 7 2 9

sample output

2 3 3 5 1

@[email protected]

网上的题解(至少在我能看到的)大多采用的是如下的方法:

我们记 dp[i][s] 表示第 i 份食物被左右两个人选择的状态为 s 是否合法(0 <= s < 4)

拆环成链:在第 n 个人的右边,即第 n + 1 份食物处放上第 1 份食物。

枚举第一份食物被吃的状态,递推到第 n + 1 份,再 check 第 n + 1 份的状态是否等于第一份食物。

dp 的时候记录转移,就可以输出方案了。

然而。。。在我的不懈努力下,我乱搞出来了另外一个解法:

首先可以发现如果一个人某一边的食物 >= 另一边的两倍,则他选择这一边的食物一定不亏。

我们称这个人具有绝对占优策略。

这并不能说明什么。但是,考虑这样一个情况:所有人都不具有绝对占优策略。

这个时候,第 i 个人选择第 i 份食物总是一个合法的解。

证明可以采用反证法:如果第 i 个人改变选择而第 i + 1 个人不改变选择导致第 i 个人收益更大,则说明 c[i + 1] > c[i]*2。矛盾。

是否我们可以去掉具有绝对占优策略的人,再将剩下的人按上面的方法(第 i 个人选择第 i 份食物)进行处理?

对,也不完全对。

因为如果一个人的策略确定了,可能会导致其他人从不确定的状态转为确定的状态。

(举个例子:某个人旁边是 6 7,是不确定的。这个时候他旁边的人选择了 6,他变成了 3 7,就是确定的了)

需要在第一次去掉过后再继续寻找新的。

如果直接迭代 “寻找具有绝对占优策略的人”->“删除他们”,时间复杂度是 O(n^2) 的。

但是,我们发现一个人的策略只会影响他左右的人的策略。

所以可以将他左右的人加入队列(为了偷懒我用的栈),再进行进一步的处理。

这样就是 O(n) 的了。

同时这也表示不会存在无解的情况。

@accepted [email protected]

给出两份代码,分别对应上面的两类解法。

第一份:

#include<cstdio>
const int MAXN = 1000000 + 5;
inline int read() {
    int x = 0; char ch = getchar();
    while( ch > ‘9‘ || ch < ‘0‘ ) ch = getchar();
    while( ‘0‘ <= ch && ch <= ‘9‘ ) x = 10*x + ch-‘0‘, ch = getchar();
    return x;
}
inline void write(int x) {
    if( !x ) return ;
    write(x/10);
    putchar(x%10 + ‘0‘);
}
int c[MAXN], pre[4][MAXN], n;
bool dp[4][MAXN];
void print(int i, int j) {
    if( j == 0 ) return ;
    print(pre[i][j], j - 1);
    printf("%d ", (j + (i&1)) > n ? 1 : (j + (i&1)) );
}
bool check(int x) {
    for(int i=0;i<=n;i++)
        dp[0][i] = dp[1][i] = dp[2][i] = dp[3][i] = false;
    dp[x][0] = true;
    for(int i=1;i<=n;i++) {
        if( dp[2][i-1] && c[i]   <= c[i-1]   ) dp[0][i] = true, pre[0][i] = 2;
        if( dp[3][i-1] && 2*c[i] <= c[i-1]   ) dp[0][i] = true, pre[0][i] = 3;
        if( dp[0][i-1] && c[i]   >= c[i-1]   ) dp[1][i] = true, pre[1][i] = 0;
        if( dp[1][i-1] && 2*c[i] >= c[i-1]   ) dp[1][i] = true, pre[1][i] = 1;
        if( dp[2][i-1] && c[i]   <= 2*c[i-1] ) dp[2][i] = true, pre[2][i] = 2;
        if( dp[3][i-1] && 2*c[i] <= 2*c[i-1] ) dp[2][i] = true, pre[2][i] = 3;
        if( dp[0][i-1] && c[i]   >= 2*c[i-1] ) dp[3][i] = true, pre[3][i] = 0;
        if( dp[1][i-1] && 2*c[i] >= 2*c[i-1] ) dp[3][i] = true, pre[3][i] = 1;
    }
    if( dp[x][n] ) {
        print(x, n);
        return true;
    }
    return false;
}
int main() {
    n = read();
    for(int i=0;i<n;i++)
        c[i] = read();
    c[n] = c[0];
    for(int i=0;i<4;i++)
        if( check(i) ) return 0;
    puts("NIE");
}

第二份:

#include<cstdio>
const int MAXN = 1000000;
int ans[MAXN + 5], tag[MAXN + 5], n;
int c[MAXN + 5], stk[MAXN + 5], top = 0;
inline int nxt(int x) {return (x + 1 == n) ? 0 : x + 1;}
inline int pre(int x) {return (x == 0) ? n - 1 : x - 1;}
inline bool Check(int i, int j) {
    if( tag[i] == -1 ) {
        if( tag[j] == 1 ) return c[i]*2 > c[j];
        else return c[i] > c[j];
    }
    else {
        if( tag[j] == 1 ) return c[i] > c[j];
        else return c[i] > c[j]*2;
    }
}
inline int read() {
    int x = 0; char ch = getchar();
    while( ch > ‘9‘ || ch < ‘0‘ ) ch = getchar();
    while( ‘0‘ <= ch && ch <= ‘9‘ ) x = 10*x + ch-‘0‘, ch = getchar();
    return x;
}
void write(int x) {
    if( !x ) return ;
    write(x/10);
    putchar(x%10 + ‘0‘);
}
int main() {
    scanf("%d", &n);
    for(int i=0;i<n;i++) {
        c[i] = read();
        ans[i] = -1; tag[i] = 0;
    }
    for(int i=0;i<n;i++) {
        if( ans[i] != -1 ) continue;
        stk[++top] = i;
        while( top ) {
            int x = stk[top--];
            if( Check(x, nxt(x)) ) {
                ans[x] = x;
                if( ans[nxt(x)] == -1 ) stk[++top] = nxt(x), tag[nxt(x)] = -1;
                if( ans[pre(x)] == -1 ) stk[++top] = pre(x), tag[x] = 1;
            }
            else if( Check(nxt(x), x) ) {
                ans[x] = nxt(x);
                if( ans[nxt(x)] == -1 ) stk[++top] = nxt(x), tag[nxt(x)] = 1;
                if( ans[pre(x)] == -1 ) stk[++top] = pre(x), tag[x] = -1;
            }
        }
    }
    for(int i=0;i<n;i++)
        if( ans[i] == -1 ) ans[i] = i;
    for(int i=0;i<n;i++) write(ans[i] + 1), putchar(‘ ‘);
    puts("");
}

@[email protected]

一查,这道题居然还有什么专业背景,叫什么帕累托最优什么的……

不过这不重要。

dp 竟然卡我内存……不开 bool 还会 MLE……

如果我的乱搞做法有什么问题(因为我也没找到跟我一样做法的人所以不知道其正确性),请务必告诉我,谢谢。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10362179.html

时间: 2024-11-02 17:29:47

@bzoj - [email protected] [POI2015] ?asuchy的相关文章

@bzoj - [email&#160;protected] [POI2015] Kinoman

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 共有 m 部电影,第 i 部电影的好看值为 w[i]. 在 n 天之中每天会放映一部电影,第 i 天放映的是第 f[i] 部. 你可以选择 l, r (1 <= l <= r <= n) ,并观看第 l, l+1, -, r 天内所有的电影. 最大化观看且仅观看过一次的电影的好

@bzoj - [email&#160;protected] [POI2015] Wilcze do?y

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的序列,你有一次机会选中一段连续的长度不超过 d 的区间,将里面所有数字全部修改为 0. 请找到最长的一段连续区间,使得该区间内所有数字之和不超过 p . input 第一行包含三个整数 n, p, d (1 <= d <= n <= 2000000,0 &

@bzoj - [email&#160;protected] [POI2015] Pustynia

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的正整数序列 a,每个数都在 1 到 10^9 范围内. 告诉你其中 s 个数,并给出 m 条信息,每条信息包含三个数 l, r, k 以及 k 个正整数,表示 a[l], a[l+1], ..., a[r-1], a[r] 里这 k 个数中的任意一个都比任意一个剩

@bzoj - [email&#160;protected] [POI2015] Logistyka

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 维护一个长度为 n 的序列,一开始都是 0,支持以下两种操作: 1.U k a 将序列中第 k 个数修改为 a. 2.Z c s 在这个序列上,每次选出 c 个正数,并将它们都减去 1,询问能否进行 s 次操作. 每次询问独立,即每次询问不会对序列进行修改. input 第一行包含两个

@bzoj - [email&#160;protected] [POI2015] Myjnie

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 家洗车店从左往右排成一排,每家店都有一个正整数价格 p[i]. 有 m 个人要来消费,第 i 个人会驶过第 a[i] 个开始一直到第 b[i] 个洗车店,且会选择这些店中最便宜的一个进行一次消费.但是如果这个最便宜的价格大于 c[i],那么这个人就不洗车了. 请给每家店指定一个

@bzoj - [email&#160;protected] [POI2015] Odwiedziny

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,树上每条边的长度都为 1 ,第 i 个点的权值为 a[i]. Byteasar 会按照某个 1 到 n 的全排列 b 走 n-1 次,第 i 次他会从 b[i] 点走到 b[i+1] 点,并且这一次的步伐大小为 c[i]. 对于一次行走,假设起点为 x,终点为

@bzoj - [email&#160;protected] [POI2015] Wycieczki

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一张 n 个点 m 条边的带权有向图,每条边的边权只可能是1,2,3中的一种. 将所有可能的路径按路径长度排序,请输出第 k 小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点. input 第一行包含三个整数 n, m, k (1<=n<=40,1<=m<

@bzoj - [email&#160;protected] [Poi2011]Lightning Conductor

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @version - [email protected] @version - [email protected] @[email protected] @[email protected] 已知一个长度为

@bzoj - [email&#160;protected] [POI2014]Hotel加强版

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵树,求无序三元组 (a, b, c) 的个数,使得 dis(a, b) = dis(b, c) = dis(c, a),且 a ≠ b, b ≠ c, c ≠ a. inpu