2013 ACM/ICPC Asia Regional Changsha Online - J
原题戳这里。
题意: 有一未知列数a1,a2,a3.....an,
已知s[i]=a[i-1]+a[i]+a[i] (1<i<n)
s[1]=a[1]+a[2];
s[n]=a[n-1]+a[n]
还知道a数列中一些数的值(可以全部不知道)
询问一些数 可能的最大值是多少。
首先,可以根据s数组求出a[i]的值(i为3 的倍数) answer[3k]=s[3k-1]-s[3k-2]+a[3k-3];
其次,要是知道连续两个数a[i] ,a[i+1],或者a[i],a[i+1],那么就能确定整个序列。,问题解决,直接回复 询问即可。
最后,要是不能确定整个序列,那么所有a[i]都是未知的(i不为3 的倍数)。下面求这些未知a[i]的可能最大值。
求a中i=3k+1位上的最大值
1.设a[1]=s[1],则a[2]=0;
2.根据a[i]=s[i-1]-a[i-1]-a[i-2],暂时求出一个可能非法(出现负值)的序列temp。
3.找到i=3k+2位上的最小值min(min<=0) (min必不大于0是因为a[2]=0)
4.为了让a[3k+2]上的数合法,把temp[3k+1] 的数降低-min(min为负数),temp[3k+1]的数上升-min。
这样temp就合法,且temp[3k+1]为最大,temp[3k+2]为最小,取temp[3k+1]作为3k+1位上的答案。
answer[3k+1]=temp[3k+1]+min;
求a中i=3k+2位上的最大值
1.设a[1]=0;a[2]=s[1];
2.上同
3.找到i=3k+1位上的最小值min
4.answer[3k+2]=temp[3k+2]+min;
一个answer数组就能离线地处理这些询问了。
PWW学姐问这么算出来为什么是最大而且合法的,当初给她的解释是在求i=3k+1时,先把temp[3k+1]做成最大,temp[3k+2]略显非法,(这时temp[3k+1]肯定是最大的),同时把temp[3k+1]做最小地牺牲(即降低一个值),使得temp[3k+2]也是合法的。同时这个牺牲显然也是最小的。所以temp[3k+1]经过调整后肯定是最大的。因为是最大的,所以temp[3k+1]肯定是非负的,否则肯定是数据错了。
当然这样是毛估估的。。。(懂“毛估估”这个词么?就是在简单得在心里蹭蹭。“蹭蹭”:=“想一想”)
证明最大性质的证明过程:
假设在3k+1这个位置的最大值不是temp[3k+1] ,而是m,则设dis=m-temp[3k+1] (dis>0)。
此时在3k+2这个位置的值应该为temp[3k+2]-dis (temp[3k+1]+temp[3k+2] 为定值)
同理3k+4,这个位置的值应该为temp[3k+2]+dis (temp[3k+2]+temp[3k+4]为定值)
即每个位置的值是temp中所有3k+1位加dis,3k+2位减dis.
而temp[3k+2]至少有一个数为0(算法流程里体现了),这是这个位置的数为-dis,负数,矛盾,非法,不成立。
证毕。
PS:看到我的两个小队友的blog了,感觉略感落伍,以前以为大牛才能有blog。谨以此文,纪念我的第一篇公开解题报告。
代码略难看。。。(羞涩)
#include <stdio.h> #include <string.h> #define MAXN 100005 #define max(a,b) a>b? a:b int n,m; int tell[MAXN],s[MAXN]; int query[1000]; int a[MAXN]; int ansmax[MAXN],ansmin[MAXN]; int temp[MAXN]; void fuck() //这种情况最恶心了,比赛就是写这种情况来不及了。。诅咒之 { temp[1]=s[1];// 3k+1位最大 temp[2]=0; int i; for (i =3; i<=n; i++) temp[i]=s[i-1]-temp[i-1]-temp[i-2]; int min=0x7ffffff; for (i=2; i<=n; i=i+3) if (min > temp[i]) min = temp[i]; for (i=1; i<=n; i=i+3) ansmax[i]=temp[i]+min; temp[n]=s[n];//3k+2位最大 temp[n-1]=0; for (i =n-2; i>=1; i--) temp[i]=s[i+1]-temp[i+1]-temp[i+2]; min=0x7ffffff; for (i=1; i<=n; i=i+3) if (min > temp[i]) min = temp[i]; for (i=2; i<=n; i=i+3) ansmax[i]=temp[i]+min; for (i = 3; i<n; i=i+3) ansmax[i]=temp[i]; ansmax[n]=max(ansmax[n],temp[n]+min); for (i=1; i<=m; i++) printf("%d\n",ansmax[query[i]+1]); } void work() { int i=2,j=3,k; memset(a,-1,sizeof(a)); a[0]=0; while (j<=n) //顺序3k位置的值 { a[j] = s[j-1]-s[j-2]+a[j-3]; j+=3; } j = n-2; a[n+1]=0; while ( j>=1) //逆序3k位置的值 { a[j] = s[j+1]-s[j+2] +a[j+3]; j-=3; } for (i=1; i<=n; i++) if (tell[i] != -1) a[i]=tell[i]; int flag=true; printf("\n"); for (i=1 ;i+2 <= n; i++) //a[i],a[i+2]已知,求a[i+1] if ( a[i] != -1 && a[i+2] != -1) a[i+1]=s[i+1]-a[i]-a[i+2]; for ( i = 1; i <n && flag; i++)//检测是否有连续两个数存在的情况 if (a[i]!= -1 && a[i+1] !=-1) { flag=false; j=i; for (k=i-1; k>=1; k--) a[k] =s[k+1]-a[k+1]-a[k+2]; for (k=i+2; k<=n; k++) a[k]= s[k-1]-a[k-1]-a[k-2]; } if (!flag) //如果有就ok了 for (k=1; k<=m; k++) printf("%d\n",a[query[k]+1]); if ( flag )//没有连续两个数。 fuck(); int main() { int i,j; while (scanf("%d",&n)!=EOF) { for (i=1; i<=n; i++) scanf("%d",&tell[i]); for (i=1; i<=n; i++) scanf("%d",&s[i]); scanf("%d",&m); for (i=1; i <=m; i++) scanf("%d",&query[i]); work(); } return 0; }