Codeforces Round #510
今天看机房大佬们找老师要外网打CF比赛,我忍不住也跟着注册了个号参加了,这是我第一次打CF比赛,打得比较菜,可能还有讨论成分,请见谅哈。
T1:Benches
题意:有$n$个长凳,初始每个长凳上面坐着$a_i$个人,现在又来了m个人,每个人会选一个长凳坐下。设新来的m个人都坐下后,人数最多的一个长凳的人数为$k$,求$k$的最小值和最大值。
题解:$k$最小:把新来的人尽量往人少的长凳上放,如果放到所有长凳人数都相等的话就均分剩下的人;$k$最大:把m个人都放到初始人数最多的长凳上。

1 #include<bits/stdc++.h> 2 using namespace std; 3 inline int read(){ 4 int x=0; bool f=1; char c=getchar(); 5 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 6 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 7 if(f) return x; 8 return 0-x; 9 } 10 int n,m,a[10005],mx,cha_sum; 11 int main(){ 12 n=read(),m=read(); 13 for(int i=1;i<=n;i++) mx=max(mx,a[i]=read()); 14 for(int i=1;i<=n;i++) cha_sum+=mx-a[i]; 15 printf("%d %d\n", m-cha_sum>0 ? mx+(m-cha_sum+(n-1))/n : mx, mx+m); 16 return 0; 17 }
T2:Vitamins
题意:有$n$瓶果汁,第i瓶的价格为$c_i$,果汁中可能包含A, B, C三种维生素。问喝齐A, B, C三种维生素所需的买果汁的最小价钱。
题解:
状态压缩3种维生素的8种存在情况,dp即可。
转移就是从其它各种能够转移的情况转移过来。我写的比较粗暴,直接设了三维状态表示每种维生素是否存在。
“从其它各种能够转移的情况转移过来”主要是因为题目只要求喝齐三种维生素,没要求每种只能喝一个,因此一种维生素可以喝多个,即存在某种维生素的状态依然可以转移到存在这种维生素的状态。
我的转移判断大致是这样:当遍历到包含某种维生素的饮料时,没有这种维生素的情况能转移到有这种维生素的情况,有这种维生素的情况也能转移到有这种维生素的情况;而遍历到不包含某种维生素的饮料时,没有这种维生素的情况能转移到没有这种维生素的情况,有这种维生素的情况也能转移到有这种维生素的情况。

1 #include<bits/stdc++.h> 2 #define N 1001 3 using namespace std; 4 int inf; 5 inline int read(){ 6 int x=0; bool f=1; char c=getchar(); 7 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 8 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 9 if(f) return x; 10 return 0-x; 11 } 12 int n,a,b[3],len,dp[2][2][2];//dp[i][j][k] 13 char c[5]; 14 int main(){ 15 memset(dp,0x7f,sizeof dp); 16 inf=dp[0][0][0]; 17 dp[0][0][0]=0; 18 n=read(); 19 int i,j,k,p; 20 for(i=1;i<=n;i++){ 21 a=read(); scanf("%s",c); 22 len=strlen(c); 23 b[0]=b[1]=b[2]=0; 24 for(j=0;j<len;j++) b[c[j]-'A']=1; 25 for(j=0;j<2;j++) 26 for(k=0;k<2;k++) 27 for(p=0;p<2;p++) 28 if(j>=b[0] && k>=b[1] && p>=b[2]){ //对应维生素数量够才能转移 29 dp[j][k][p]=min(dp[j][k][p],dp[j-b[0]][k-b[1]][p-b[2]]+a); 30 if(b[0]) dp[j][k][p]=min(dp[j][k][p],dp[j][k-b[1]][p-b[2]]+a); 31 if(b[1]) dp[j][k][p]=min(dp[j][k][p],dp[j-b[0]][k][p-b[2]]+a); 32 if(b[2]) dp[j][k][p]=min(dp[j][k][p],dp[j-b[0]][k-b[1]][p]+a); 33 if(b[0] && b[1]) dp[j][k][p]=min(dp[j][k][p],dp[j][k][p-b[2]]+a); 34 if(b[0] && b[2]) dp[j][k][p]=min(dp[j][k][p],dp[j][k-b[1]][p]+a); 35 if(b[1] && b[2]) dp[j][k][p]=min(dp[j][k][p],dp[j-b[0]][k][p]+a);//printf("7:%d\n",dp[j][k][p]); 36 } 37 } 38 if(dp[1][1][1]==inf) printf("-1\n"); 39 else printf("%d\n",dp[1][1][1]); 40 return 0; 41 }
T3:Array Product
题意:一个序列有$n$个数。你可以合并任意两个数,即先删除两个数,再将两数相乘的值放到右边那个数的位置;同时你还有$1$次机会直接删除序列中的一个数。问做$n-1$次前两种操作后序列中剩下的唯一的数最大可以是多少。
题解:
找规律题,很容易发现对于普通的合并操作,如果合并的数都不变,最终相乘合并的结果与合并顺序无关。而只有“直接删除一个数”操作能修改一次合并的数,于是这个操作怎么用就是关键。
首先,如果只用合并操作,在序列中有奇数个负数的情况下,最终乘积是负数。删去最大(绝对值最小)的一个负数,即让剩下的数的乘积变为正数 且 让所有负数的乘积为正数并最大 肯定更优;
其次,如果只用合并操作,在序列中存在0的情况下,最终乘积是0。删去这些0,让剩下的数的乘积变为正数 肯定更优。
但是第二种情况如何删除所有的0?另外上面两种情况同时存在怎么办?
考虑合并解决这些不需要的数,把这些要删除的数都相乘合并起来,然后一次删除它们合并出的数。这样就完美利用合并操作解决了只能进行一次删除操作的限制。
注意只有一个负数、全是0等极端情况,这些情况下千万不要多输出一步操作(比如序列的n个数都是0,你合并了n-1次0后又删了一次0,总共变成了n次)。

1 #include<bits/stdc++.h> 2 #define LL long long 3 #define INF 2147483647 4 #define MAXN 100+100 5 #define rep(i,s,t) for(int i=s;i<=t;++i) 6 #define dwn(i,s,t) for(int i=s;i>=t;--i) 7 8 using namespace std; 9 10 const int maxn=1000000+1010; 11 12 inline int read(){ 13 int x=0,f=1;char ch=getchar(); 14 while(!isdigit(ch)&&ch!='-')ch=getchar(); 15 if(ch=='-')f=-1,ch=getchar(); 16 while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); 17 return x*f; 18 } 19 20 inline void write(int x){ 21 int f=0;char ch[20]; 22 if(!x){putchar('0'),putchar('\n');return;} 23 if(x<0)x=-x,putchar('-'); 24 while(x)ch[++f]=x%10+'0',x/=10; 25 while(f)putchar(ch[f--]); 26 putchar('\n'); 27 } 28 29 int n,a[maxn],tot,l[maxn],tot1,xy0=-2147483646,xy1=-1; 30 bool book[maxn]; 31 int main(){ 32 n=read(); 33 for(int i=1;i<=n;i++){ 34 a[i]=read(); 35 if(a[i]==0)l[++tot]=i; 36 if(a[i]<0){ 37 tot1++; 38 if(xy0<a[i])xy0=a[i],xy1=i; 39 } 40 } 41 int m=n; 42 if(n==1)return 0; 43 if(tot1%2==1){ 44 if(tot>=1)printf("1 %d %d\n",xy1,l[1]); 45 else { 46 printf("2 %d\n",xy1); 47 } n--; 48 book[xy1]=1; 49 if(n==1)return 0; 50 } 51 for(int i=1;i<tot;i++){ 52 printf("1 %d %d\n",l[i],l[i+1]); 53 book[l[i]]=1; n--; 54 if(n<=1)return 0; 55 } 56 if(tot>=1){n--;book[l[tot]]=1;printf("2 %d\n",l[tot]);} 57 if(n<=1)return 0; queue<int>q; 58 for(int i=1;i<=m;i++)if(!book[i])q.push(i); 59 while(!q.empty()){ 60 if(n<=1)return 0; 61 int u=q.front(); 62 q.pop(); 63 n--; 64 int x1=q.front(); 65 printf("1 %d %d\n",u,x1); 66 } 67 return 0; 68 }
T4:Petya and Array
题意:一个序列有$n(n \leq 200000)$个数,问这个序列中总共有多少个和小于等于$t(t \leq 10^{14})$。
题解:
由于负数的存在,不能直接滑动窗口,因此这就是一道裸的数据结构题了。
设$sum[i]$表示第1到i个数的和,则题意转化为满足$sum[r]-sum[l-1]<=t | 1<=l<=r$的个数。
移项得到$sum[r]-t<=sum[l-1]$。
目前我知道3种方法:
1、树状数组,将上式看做为求逆序对,找前$i-1$个前缀中小于等于$sum[r]-t$的前缀数量。但是开$10^{14}$以上大小的树状数组不现实,因此把$n$个前缀和离散化后插入$n$大小的树状数组即可。
2、平衡树,同上,求$sum[r]-t$在前$i-1$个前缀和中从小到大的排名(其实也是求满足条件的前缀数量)。平衡树的每个点都代表一个前缀和,因此最多只有$n$个点。
3、值域线段树,由于有$10^{14}$的大小,因此要动态开点,粗略计算最多建$200000*log2(10^{14})$个点,大约1000w个点吧。算上常数,这个做法时间不太稳,可能超时。(没试)

1 #include<bits/stdc++.h> 2 #define ll long long 3 #define maxn 1000005 4 using namespace std; 5 inline ll read(){ 6 ll x=0; bool f=1; char c=getchar(); 7 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 8 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 9 if(f) return x; 10 return 0-x; 11 } 12 13 int n; 14 ll t; 15 namespace Treap{ 16 int rnd[maxn],cnt[maxn]; 17 int rt,size,lch[maxn],rch[maxn],big[maxn]; 18 ll num[maxn]; 19 void init(){rt=size=0; return;} 20 21 inline void update(int u){ 22 big[u] = big[lch[u]]+big[rch[u]]+cnt[u]; 23 return; 24 } 25 26 inline void lturn(int &u){ 27 ll t=rch[u]; 28 rch[u]=lch[t], lch[t]=u; 29 big[t]=big[u], update(u), u=t; 30 return; 31 } 32 33 inline void rturn(int &u){ 34 ll t=lch[u]; 35 lch[u]=rch[t], rch[t]=u; 36 big[t]=big[u], update(u), u=t; 37 return; 38 } 39 40 void insert(int &cur,ll x){ 41 if(cur==0){ 42 cur=++size, num[cur]=x; 43 cnt[cur]=1, rnd[cur]=rand(); 44 big[cur]=1; return; 45 } 46 big[cur]++; 47 if(num[cur]==x)cnt[cur]++; 48 else if(x>num[cur]){ 49 insert(rch[cur],x); 50 if(rnd[rch[cur]]<rnd[cur]) lturn(cur); 51 } 52 else{ 53 insert(lch[cur],x); 54 if(rnd[lch[cur]]<rnd[cur]) rturn(cur); 55 } 56 return; 57 } 58 59 void del(int &k,ll x){ 60 if(k==0) return; 61 else if(num[k]==x){ 62 if(cnt[k]>1) {cnt[k]--,big[k]--; return;} 63 if(lch[k]*rch[k]==0) {k=lch[k]+rch[k]; return;} 64 if(rnd[lch[k]]>rnd[rch[k]]) lturn(k), del(lch[k],x); 65 else rturn(k), del(rch[k],x); 66 } 67 else if(num[k]>x) big[k]--, del(lch[k],x); 68 else big[k]--, del(rch[k],x); 69 update(k); 70 return; 71 } 72 73 int query(int cur,ll x){ 74 if(cur==0) return 0; 75 if(num[cur]<=x) return query(rch[cur],x); 76 else return big[cur]-big[lch[cur]]+query(lch[cur],x); 77 } 78 } 79 using namespace Treap; 80 81 int main(){ 82 init(); ll ans=0; 83 n=read(),t=read(); 84 insert(rt,0); 85 for(ll i=1,s=0;i<=n;i++){ 86 s+=read(); 87 ans+=query(rt,s-t); 88 insert(rt,s); 89 } 90 printf("%I64d\n",ans); 91 return 0; 92 }