CSUST--4.4排位周赛第七场 (全解)

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;
}
View Code

 

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\n");
    else printf("%d\n",ans);
    return 0;
}
View Code

 

创造创造

题目大意:给你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\n",1LL*(a[n]-a[1])*(a[n*2]-a[n+1]));
    return 0;
}
View Code

 

这才是真正的冰阔落

题目大意:有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\times 2^{n-i}$($i$表示剩下i张50元)的结果。。。。然后我去群里质疑了。。。然后我就被喷了QAQ

我们设当下已经有x个人拿出50元,y个人拿出了100元,$x\in [0,n],y\in [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\times 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}})\times C_n^k$

不过还没结束。。。。接下来我们还要想想组合数对p取模的问题,由于p不一定是素数,而n不是非常大,所以可以把每个组合数展开来求,求出每个分子分母的质因子及其次数,然后约分之后,用快速幂求出整个组合数。我们可以预处理出1到n中每个数的质因子及其次数,即node num[i][j]{pos,cnt}表示$i$质因子$pos$有$cnt$个但可惜的是1e5以内的素数大概有1e4个,预处理的复杂度就是$1e5\times 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 \n",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\n",ans);
    return 0;
}
View Code

 

喝可乐   

题目大意:给你一个长度为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\n",ans_max-ans_min);
    return 0;
}
View Code

 

posted @ 2020-04-12 18:31  lonely_wind  阅读(285)  评论(0编辑  收藏  举报