BZOJ3749 POI2015 Łasuchy


Description

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

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

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

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

Input

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

第二行包含n个整数 c[1],c[2],,c[n](1<=c[i]<=109)

第i个人 (1i<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


显然对于一个人,他有4种选择的方式:

  • 选第i个食物的c[i]热量(此时必须要有i-1那个人选择c[i-1])。
  • 选第i个食物的c[i]/2热量(此时必须要有i-1选择c[i])。
  • 选第i%n+1个食物的c[i%n+1]热量(此时必须要有i%n+1选择c[i%n+2])。
  • 选第i%n+1个食物的c[i%n+1]/2热量(此时必须要有i%n+1选择c[i%n+1])。

按照上述四种转移方式,我们可以完全确定一些人的选择(如c[i]/2 < <script type="math/tex" id="MathJax-Element-72"><</script>c[i+1],此时无论如何一定选c[i+1]更优)。

之后就在想一些奇怪的转移了,譬如说样例数据都不是唯一解什么的……但是压根就没往dp上想。(我大概是在有意回避这类比较麻烦的dp转移,树形dp练多了只觉得树形dp更优美好懂一些,普通dp显得有点太依赖状态了,然后我自己又特别害怕从题目抽取复杂状态的问题……)

错误的转移示例

我们就按照上述4个转移,分别定义状态0~3一一对应,定义 dp[case][j] 表示到第j个人,选择第case种转移方式进行转移,可以得到的前面第j-1个人的何种转移(为了统一,都从上一个人进行转移更新)。

于是必须要有一个一开始的状态,我们就枚举第1个人的状态好了。假设第一个人原本的状态为kase,那么当第n个人结束对第1个人的转移后,只需要第一个人在之前枚举的位置上有转移,就已经得到一个合法方案了。

说的像上面的定义能够转移似的(爆肝都没用好么)。由于在取一半能量的时候,我们需要上上次的转移,所以无法进行更新(因为转移是有多重情况的,不好讨论)。

正确的转移示例

我们定义 dp[case][j] 表示对于第j个食物,选择第case种情况进行转移(所有情况:没有被选到(0),左侧人选(1),右侧人选(2),两侧人平摊(3))(所以基本定义和Claris的不一样)。

此时再考虑第j-1个食物第j个食物之间的转移方程式:

dp[0][j]={2dp[2][j1]3dp[3][j1]c[j1]c[j]c[j1]/2c[j]

dp[1][j]={0dp[0][j1]1dp[1][j1]c[j1]c[j]c[j1]/2c[j]

dp[2][j]={2dp[2][j1]3dp[3][j1]c[j1]c[j]/2c[j1]/2c[j]/2

dp[3][j]={0dp[0][j1]1dp[1][j1]c[j1]c[j]/2c[j1]/2c[j]/2

蜜汁对称性

显然此时的dp数组就是fa数组了,按照dp数组倒推即可。

#include <bits/stdc++.h>
#define M 1000005
using namespace std;
inline void Rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>47);
}
int c[M],dp[4][M];
void move(int pre,int now){
    if(~dp[2][pre]&&c[pre]>=c[now])dp[0][now]=2;
    else if(~dp[3][pre]&&c[pre]>=c[now]*2)dp[0][now]=3;//dp[0][now]
    if(~dp[0][pre]&&c[pre]<=c[now])dp[1][now]=0;
    else if(~dp[1][pre]&&c[pre]<=c[now]*2)dp[1][now]=1;//dp[1][now]
    if(~dp[2][pre]&&c[pre]*2>=c[now])dp[2][now]=2;
    else if(~dp[3][pre]&&c[pre]>=c[now])dp[2][now]=3;//dp[2][now]
    if(~dp[0][pre]&&c[pre]*2<=c[now])dp[3][now]=0;
    else if(~dp[1][pre]&&c[pre]<=c[now])dp[3][now]=1;//dp[3][now]
}
int ans[M];
int main(){
    int n;Rd(n);
    for(int i=1;i<=n;i++)Rd(c[i]);
    for(int S=0;S<4;S++){
        memset(dp,-1,sizeof(dp));
        dp[S][1]=4;
        for(int j=2;j<=n;j++)move(j-1,j);
        move(n,1);
        if(dp[S][1]<4){//被重新更新到了
            int step=dp[S][1];
            for(int i=n;i>=1;i--){
                if(step==1||step==3)ans[(i+n-2)%n+1]=i;
                if(step==2||step==3)ans[i]=i;
                step=dp[step][i];
            }
            for(int i=1;i<=n;i++)
                printf("%d%c",ans[i],i==n?'\n':' ');
            return 0;
        }
    }
    puts("NIE");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值