场外选手赛时只口胡出了CD感觉非常惨。只看了E并且还没看到题面里的wiki我能咋办
C:f只与gcd(n,k)有关。
D:考虑每种起始位置,对于跨越的两个排列,只有前一个排列的后缀单减时不产生贡献。答案就非常显然了。注意最后+1,因为这样没考虑n~1的排列。
E:根据题面给出的定理,n+1号点度数增大时,以该点在已从大到小排序的序列里的位置为分割点,k较大时不等式更难满足,k较小时不等式更易满足,且分割点位置左移。因此每一个位置存在一个连续的合法区间,区间并即为答案,显然其也是一个连续区间。找到任意一个合法答案后二分即可。这个任意答案也可以二分得到,具体地,根据n+1号点度数所在位置及其右边位置是否合法决定二分方向。不等式显然可以树状数组优化一下检验(甚至可以线性?)。
#include#include #include #include #include #include using namespace std;#define ll long long#define N 500010char getc(){ char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}int gcd(int n,int m){ return m==0?n:gcd(m,n%m);}int read(){ int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') { if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f;}int n,a[N],b[N],tree_size[N],isodd,down,up;ll tree_sum[N];void ins(int x){ for (int i=x+1;i<=n+1;i+=i&-i) tree_size[i]++,tree_sum[i]+=x;}int query_size(int k){ int s=0;k++;while (k) s+=tree_size[k],k-=k&-k;return s;}ll query_sum(int k){ll s=0;k++;while (k) s+=tree_sum[k],k-=k&-k;return s;}int findanyans(){ int l=0,r=(n>>1)+1; while (l<=r) { int mid=l+r>>1,k=mid<<1|isodd; if (k>n) r=mid-1; else { memset(tree_sum,0,sizeof(tree_sum)); memset(tree_size,0,sizeof(tree_size)); for (int i=1;i<=n;i++) b[i]=a[i];b[n+1]=0; int pos=0; for (int i=1;i<=n+1;i++) if (b[i] i;j--) b[j]=b[j-1]; b[i]=k;pos=i;break; } if (pos==0) pos=n+1; ll s=0;for (int i=1;i<=n+1;i++) s+=b[i]; bool flag=1; for (int i=n+1;i>=pos;i--) { if (s>1ll*i*(i-1)+1ll*i*((n+1-i)-query_size(i))+query_sum(i)) {flag=0;break;} s-=b[i];ins(b[i]); } if (!flag) r=mid-1; else { for (int i=pos-1;i>=1;i--) { if (s>1ll*i*(i-1)+1ll*i*((n+1-i)-query_size(i))+query_sum(i)) {flag=0;break;} s-=b[i];ins(b[i]); } if (flag) return mid;else l=mid+1; } } } return -1;}bool check(int mid){ mid<<=1,mid+=isodd; if (mid>n) return 0; memset(tree_sum,0,sizeof(tree_sum)); memset(tree_size,0,sizeof(tree_size)); for (int i=1;i<=n;i++) b[i]=a[i];b[n+1]=0; for (int i=1;i<=n+1;i++) if (b[i] i;j--) b[j]=b[j-1]; b[i]=mid;break; } ll s=0;for (int i=1;i<=n+1;i++) s+=b[i]; for (int i=n+1;i>=1;i--) { if (s>1ll*i*(i-1)+1ll*i*((n+1-i)-query_size(i))+query_sum(i)) return 0; s-=b[i];ins(b[i]); } return 1;}int main(){#ifndef ONLINE_JUDGE freopen("e.in","r",stdin); freopen("e.out","w",stdout); const char LL[]="%I64d\n";#else const char LL[]="%lld\n";#endif n=read(); for (int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1);reverse(a+1,a+n+1); for (int i=1;i<=n;i++) isodd^=a[i]&1; down=up=findanyans(); if (down==-1) {cout<<-1;return 0;} int l=0,r=down; while (l<=r) { int mid=l+r>>1; if (check(mid)) down=mid,r=mid-1; else l=mid+1; } l=up,r=(n>>1)+1; while (l<=r) { int mid=l+r>>1; if (check(mid)) up=mid,l=mid+1; else r=mid-1; } for (int i=down;i<=up;i++) printf("%d ",i<<1|isodd); return 0;}
F:显然每一段都是前一部分步行/游泳,后一部分飞。同样显然的是最后把精力值用完才是最优的,于是总飞行时间也固定了。现在所要做的就是尽量使用游泳来攒精力。那么当处于游泳路段时,只要精力值不会多余就攒精力,否则直接飞到终点。处于步行路段时,只要能满足之后强制飞行路段的需求就飞,否则步行攒精力。怎么这么简单啊?然后才发现原来还有原地徘徊这种操作。不过事实上只要根据后面的需求延长路径且尽量在游泳路段延长就行了?