Good Bye 2018 沒打記
阿新 • • 發佈:2018-12-31
場外選手賽時只口胡出了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'View Code||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:顯然每一段都是前一部分步行/游泳,後一部分飛。同樣顯然的是最後把精力值用完才是最優的,於是總飛行時間也固定了。現在所要做的就是儘量使用游泳來攢精力。那麼當處於游泳路段時,只要精力值不會多餘就攢精力,否則直接飛到終點。處於步行路段時,只要能滿足之後強制飛行路段的需求就飛,否則步行攢精力。怎麼這麼簡單啊?然後才發現原來還有原地徘徊這種操作。感覺上也沒有變複雜多少,但實在搞不動了。