标签:etc closed hide 一段 最优 str class size 数组
场外选手赛时只口胡出了CD感觉非常惨。
C:f只与gcd(n,k)有关。
D:考虑每种起始位置,对于跨越的两个排列,只有前一个排列的后缀单减时不产生贡献。答案就非常显然了。注意最后+1,因为这样没考虑n~1的排列。
E:根据题面给出的定理,n+1号点度数增大时,以该点在已从大到小排序的序列里的位置为分割点,k较大时不等式更难满足,k较小时不等式更易满足,且分割点位置左移。因此每一个位置存在一个连续的合法区间,区间并即为答案,显然其也是一个连续区间。找到任意一个合法答案后二分即可。这个任意答案也可以二分得到,具体地,根据n+1号点度数所在位置及其右边位置是否合法决定二分方向。不等式显然可以树状数组优化一下检验(甚至可以线性?)。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 500010 char 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]<k) { for (int j=n+1;j>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]<mid) { for (int j=n+1;j>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:显然每一段都是前一部分步行/游泳,后一部分飞。同样显然的是最后把精力值用完才是最优的,于是总飞行时间也固定了。现在所要做的就是尽量使用游泳来攒精力。那么当处于游泳路段时,只要精力值不会多余就攒精力,否则直接飞到终点。处于步行路段时,只要能满足之后强制飞行路段的需求就飞,否则步行攒精力。怎么这么简单啊?然后才发现原来还有原地徘徊这种操作。感觉上也没有变复杂多少,但实在搞不动了。
标签:etc closed hide 一段 最优 str class size 数组
原文地址:https://www.cnblogs.com/Gloid/p/10202031.html