emmmm,你们tql,这场比赛蒟蒻的我都完全hold不住
题目说明:
对决(线段树|树状数组)
Rap男孩(DP)
创造创造(思维)
这才是真正的冰阔落(数论)
喝可乐(二分)
比赛链接:http://acm.csust.edu.cn/contest/86/problems
比赛过后无法提交,请到problem中提交
对决
题目大意:给你n种数据,每种数据有两种属性值,力量值$x$和敏捷值$y$,当两个人$a,b$的属性值$x_{a}>=x_{b} , y_{a}<=y_{b}$为激烈的对决,问总共有几种激烈的对决方式。
Sample Input
3 3 2 1 1 2 3
Sample Output
1
emmmm,先搞懂题目在说什么,写个暴力看看是T了还是怎么了:
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; struct node { int x,y; }pt[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ int x,y; scanf("%d%d",&x,&y); pt[i]=node{x,y}; } int ans=0; for (int i=1; i<=n; i++){ for (int j=i+1; j<=n; j++){ if ((pt[i].x>=pt[j].x && pt[i].y<=pt[j].y) || (pt[i].x<=pt[j].x && pt[i].y>=pt[j].y)) ans++; } } printf ("%d",ans); return 0; }
没什么毛病,就是T了,T了一半,A了一半。
然后。。。大概就是找一个属性大于,另一个属性小于的意思,这个我们可以利用线段树来搞,$x_{a}>=x_{b} , y_{a}<=y_{b}$,即在将y作为坐标,在$y_{b}$之前有几个属性$x$大于他的。这个我们应该不难想到对$x$排序,然后。。。此题结束
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 const int mac=2e5+10; struct node { int x,y; bool operator<(const node &a)const{ return x>a.x; } }pt[mac]; int tree[mac<<2]; void update(int l,int r,int rt,int pos,int val) { if (l==r){ tree[rt]+=val; return; } int mid=(l+r)>>1; if (pos<=mid) update(lson,pos,val); else update(rson,pos,val); tree[rt]=tree[rt<<1]+tree[rt<<1|1]; } int query(int l,int r,int rt,int L,int R) { if (l>=L && r<=R) return tree[rt]; int sum=0; int mid=(l+r)>>1; if (L<=mid) sum+=query(lson,L,R); if (R>mid) sum+=query(rson,L,R); return sum; } int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ int x,y; scanf("%d%d",&x,&y); pt[i]=node{x,y}; } long long ans=0; sort(pt+1,pt+1+n); for (int i=1; i<=n; i++){ update(1,n,1,pt[i].y,1); if (pt[i].y-1==0) continue; ans+=query(1,n,1,1,pt[i].y-1); } printf ("%lld",ans); return 0; }
Rap男孩
题目大意:给你n个操作和一个基础值x,要么将当前的值下降a[i],要么上升a[i],但必须在[0,ma]之间,输出最后的最大值,如果无法避免越界,则输出-1
Sample Input
3 5 10 5 3 7
Sample Output
10
emmmm,这是个dp题,转移的方式比较奇特。。。可以理解的是一个格子最多只有[0,ma]种值,那么由于n和ma非常小,我们可以直接枚举第i-1个格子的值,然后由着些值通过-a[i]和+a[i]变成第i个格子的值,那么我们最后直接从大到小扫一遍看看dp[n][i]是否存在就OK了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; int a[100],dp[60][1010]; int main() { int n,x,ma; scanf ("%d%d%d",&n,&x,&ma); for (int i=1; i<=n; i++) scanf ("%d",&a[i]); dp[0][x]=1; for (int i=1; i<=n; i++){ for (int j=0; j<=ma; j++){ if (dp[i-1][j] && j+a[i]<=ma) dp[i][j+a[i]]=1; if (dp[i-1][j] && j-a[i]>=0) dp[i][j-a[i]]=1; } } int mark=0,ans=0; for (int i=ma; i>=0; i--) if (dp[n][i]) {ans=i,mark=1;break;} if (!mark) printf("-1 "); else printf("%d ",ans); return 0; }
创造创造
题目大意:给你n个坐标(x,y)让你找到一个最小的矩形覆盖他们,问最小矩形的面积是多少,其中这些坐标的x可以和这些坐标的y互换。(n<=1e5)
Sample Input
4 4 1 3 2 3 2 1 3
Sample Output
1
感觉题目有点模糊,x和某些y互换了,那么这些y再和一些x互换了,那么就可以看做是x和x互换了,就是不知道这样是否符合规则,不过看一下输出,发现是整数,那么说明这样是符合规则的,不然的话并不能保证最小矩形是整数。那么也就是说这些x,y的属性是没什么卵用的,我们直接2n个数值存在一起排个序,而要挑出n个做x坐标,n个做y坐标,那么就很明显了,前n个一组a,后n个一组b就完事了,否则的话,如果在a中混进一个b组的,那么毫无疑问面积x坐标的极差会拉大,而同时,b组中也一定会混进a组的,y坐标的极差也会拉大,那么就会导致整个矩形的面积变大,所以这就是一个最小的矩形了。
以下是AC代码;
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; int a[mac<<1]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=2*n; i++){ scanf ("%d",&a[i]); } sort(a+1,a+1+2*n); printf ("%lld ",1LL*(a[n]-a[1])*(a[n*2]-a[n+1])); return 0; }
这才是真正的冰阔落
题目大意:有n个人买可乐,每瓶50元,每个人的付钱方式只能是50元,100元和银行卡,商家没有零钱不找零,同时银行卡有足够的钱,但他们都只能为自己付款,不能代付,问最后收费处还剩L到R张50元的合理方案数(每个人对商家没有欠款),对p取模。输入n,p,l,r(n,l,r<=1e5,p<=2e9)
Sample Input
4 100 0 4
Sample Output
35
emm,看到这题,首先我想到了前缀和,他需要的是L到R张50元合理的方案数,那么我们将每种情况的方案数算出来就好了,然后将L到R区间里面的加起来就完事了。至于每种情况怎么算,但我可能语文太lj了,这里隐含了很多条件我都没看出来,一是商家没有零钱的时候不会接受100元的钱,二是有零钱的时候接受100元的钱并退回50元零钱。。。而我一直以为商家不找零,也会接受100元的,剩下的50元就被吞了,于是得出了了$C_n^i imes 2^{n-i}$($i$表示剩下i张50元)的结果。。。。然后我去群里质疑了。。。然后我就被喷了QAQ
我们设当下已经有x个人拿出50元,y个人拿出了100元,$xin [0,n],yin [0,frac{n}{2}]$,而且在每个时刻一定有都有$y<=x$,剩下的就是卡的数量,我们设为z,而很明显,x和y的组合的数量就是卡特兰数的合法路径数即不越过x=y这条线的数量,即:$C_{x+y}^x-C_{x+y}^{x-1}$。我们枚举x与y的总和,得到当x+y为某个值(记为k)的时候x,y的组合数量(num)有多少,而剩下的z即有组合$C_n^{n-k}=C_n^k=use$个那么该枚举为$num imes use$个。
枚举总和的时候,也需要枚举x的值,那么时间就远远不够了,所以我们需要简化一下,剩余$i$张50元,那么也就是说有(k-i)/2张50元和(k-i)/2张100元的抵消了,剩下了$i$张50元,由于[L,R]为连续整数区间,每个相邻的数相差1,那么对于每个枚举的x,y的组合数数量从L到R就会有:$C_k^{frac{k-L}{2}}-C_k^{frac{k-L}{2}-1}+C_k^{frac{k-L}{2}-1}-C_k^{frac{k-L}{2}-2}+...+C_k^{frac{k-R}{2}}-C_k^{frac{k-R-1}{2}}$
那么就可以得到:$C_k^{frac{k-L}{2}}-C_k^{frac{k-R-1}{2}}$,那么接下来就是枚举k从0到n了将每个答案的ans加在一起,最后答案也就是$sum_{k=0}^{n}(C_k^{frac{k-L}{2}}-C_k^{frac{k-R-1}{2}}) imes C_n^k$
不过还没结束。。。。接下来我们还要想想组合数对p取模的问题,由于p不一定是素数,而n不是非常大,所以可以把每个组合数展开来求,求出每个分子分母的质因子及其次数,然后约分之后,用快速幂求出整个组合数。我们可以预处理出1到n中每个数的质因子及其次数,即node num[i][j]{pos,cnt}表示$i$质因子$pos$有$cnt$个但可惜的是1e5以内的素数大概有1e4个,预处理的复杂度就是$1e5 imes 1e4$直接爆炸。。。。
所以这里还有另一种做法就是利用欧拉函数求出所有和p互质的数,然后把所有数拆为不含p因子的数和含p因子的数,然后前者做正常的乘法,除法。后者做加,减法,最后统一快速幂乘。
即对于一个组合数$C_k^m=frac{k!}{m!(k-m)!}$取模就是$k!*inv(m!)*inv((k-m)!)$所以我们要求的就是不含p公因子的阶乘的逆元
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define debug printf ("ciaodddf") #define ll long long const int mac=1e5+10; int n,mod,p[mac],cnt; int num[mac][50],f[mac],inv[mac]; ll qick(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } void init() { int phi=mod,use=mod;//phi表示p以内和p互质的数量 for (int i=2; i<=use/i; i++){ if (use%i==0){ p[++cnt]=i;//p的素数因子i phi=phi/i*(i-1); while (use%i==0) use/=i; } } if (use>1) phi=phi/use*(use-1),p[++cnt]=use; //欧拉函数到此结束 f[0]=inv[0]=1; for (int i=1; i<=n; i++){//计算阶乘的在mod下的非公因子的逆元 int x=i; for (int j=1; j<=cnt; j++){ num[i][j]=num[i-1][j]; while (x%p[j]==0){ num[i][j]++;//i,mod含公共素因子p[j]有多少个 x/=p[j]; } } f[i]=1LL*f[i-1]*x%mod;//非公因子的乘积 inv[i]=qick(f[i],phi-1);//f[i]和mod互质,利用欧拉定理求f[i]的逆元 //printf ("%d %d ",f[i],inv[i]); } } ll C(int k,int m) { if (m<0 || k<0 || k<m) return 0; if (m==0) return 1; ll ans=(1LL*f[k]*inv[m]%mod)*inv[k-m]%mod;//C(k,m)=(k!)/(m!(k-m)!) //debug; for (int i=1; i<=cnt; i++){ ans=ans*qick(p[i],num[k][i]-num[m][i]-num[k-m][i])%mod;//将少乘的公共素因子乘回去 } //printf ("%lld ",ans); return ans; } int main() { int P,l,r; scanf ("%d%d%d%d",&n,&P,&l,&r); mod=P; init(); ll ans=0; for (int k=0; k<=n; k++){ ans+=((C(k,(k-l)>>1)-C(k,((k-r-1)>>1))+mod)%mod)*C(n,k)%mod; ans%=mod; } printf ("%lld ",ans); return 0; }
喝可乐
题目大意:给你一个长度为n的序列,你可以操作k次,每次将最大的数匀1给最小的数,问k次操作之后的极差最小值是多少
Sample Input
4 100 1 1 10 10
Sample Output
1
emmm,出的题都是奇奇怪怪的。。。极差最小值,也就是最大值和最小值之差,那么我们二分就可以了,二分k天后最大值允许达到的最小值和最小值允许达到的最大值。然后这题就over了。。。至于怎么判断其是否允许也不是很难,最小值最大也就在平均值徘徊,最大值最小也是一样的,我们将原大于最大值的数全部减去最大值求得下降到该值需要多久,判断是否大于k天即可
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=5e5+10; const int inf=1e9+10; int a[mac],n,k; long long sum=0,tot; int ok_max(int x) { int p=sum/n; if (sum%n) p++; if (x<p) return 0; long long days=0; for (int i=1; i<=n; i++){ if (a[i]>x) days+=a[i]-x; else break; } if (days>tot) return 0; return 1; } int ok_min(int x) { int p=sum/n; if (x>p) return 0; long long days=0; for (int i=1; i<=n; i++){ if (a[i]<x) days+=x-a[i]; } if (days>tot) return 0; return 1; } bool cmp(int x,int y){return x>y;} int main() { scanf ("%d%d",&n,&k); tot=k; int l=inf,r=-inf; for (int i=1; i<=n; i++){ scanf("%d",&a[i]); l=min(a[i],l); r=max(a[i],r); sum+=a[i]; } sort(a+1,a+1+n,cmp); int ans_max,ans_min; int l1=l,r1=r,mid; while (l1<=r1){ mid=(l1+r1)>>1; if (ok_max(mid)){ ans_max=mid;//最大值最小是多少 r1=mid-1; } else l1=mid+1; } int l2=l,r2=r; while (l2<=r2){ mid=(l2+r2)>>1; if (ok_min(mid)){ ans_min=mid;//最小值最大是多少 l2=mid+1; } else r2=mid-1; } printf("%d ",ans_max-ans_min); return 0; }