CCPC-Wannafly Winter Camp Day3 (Div2, onsite)

目录

 

A.二十四点*

B.集合

D.精简改良

F.小清新数论*

G.排列

H.涂鸦*

I.石头剪刀布


A.二十四点*

这题24点的规则不需要用完所有的数字,所以只要子序列能计算出24点该序列就满足条件。

一共就两个数据,第一个样例,第二个数据1~10,答案是891。

给状压开一个set存储所有能计算的数字,更新的时候枚举子状态和子状态存储的值,进行加减乘除运算。

会存在一些恶心的数据需要用到小数,所以要用到分数的处理。场上没有考虑这种,写了个不整除就跳过也能过,也非常快。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+2e2;
int lcm(int x,int y){
    return x*y/__gcd(x,y);
}
struct Fen{
    int fz,fm;
    Fen(){fz=0,fm=0;}
    Fen(int fz,int fm):fz(fz),fm(fm){}
    void operator = (const Fen o){
        fz=o.fz;fm=o.fm;
    }
    bool operator == (const Fen o)const{
        return fz==o.fz&&fm==o.fm;
    }
    bool operator < (const Fen o)const{
        int lc=lcm(fm,o.fm);
        return lc/fm*fz<lc/o.fm*o.fz;
    }
    Fen operator + (const Fen o){
        Fen p=Fen(fz,fm); int lc=lcm(p.fm,o.fm); p.fz=lc/p.fm*p.fz+lc/o.fm*o.fz; p.fm=lc;
        int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
    }
    Fen operator - (const Fen o){
        Fen p=Fen(fz,fm); int lc=lcm(p.fm,o.fm); p.fz=lc/p.fm*p.fz-lc/o.fm*o.fz; p.fm=lc;
        int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
    }
    Fen operator * (const Fen o){
        Fen p=Fen(fz,fm); p.fz*=o.fz; p.fm*=o.fm;
        if(p.fz==0||p.fm==0) return p;
        int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
    }
    Fen operator / (const Fen o){
        Fen p=Fen(fz,fm); p.fz*=o.fm; p.fm*=o.fz;
        if(p.fz==0||p.fm==0) return p;
        int gc=__gcd(p.fz,p.fm); if(gc) p.fz/=gc,p.fm/=gc; return p;
    }
};
set<Fen>dp[N];
int main()
{
    int n,x;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&x);
        dp[1<<i].insert(Fen(x,1));
    }
    Fen tg=Fen(24,1);
    int cnt=0;
    for(int s=0;s<(1<<n);s++){
        bool flag=false;
        for(int i=s&(s-1);i;i=(i-1)&s){
            int j=s-i;
            set<Fen>::iterator it,jt;
            for(it=dp[i].begin();it!=dp[i].end();it++){
                for(jt=dp[j].begin();jt!=dp[j].end();jt++){
                    Fen ans;
                    Fen ta=*it;
                    Fen tb=*jt;
                    if(ta==tg) flag=true,dp[s].clear(),dp[s].insert(ta);
                    if(tb==tg) flag=true,dp[s].clear(),dp[s].insert(tb);
                    if(flag==true) break;
                    ans=ta+tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
                    ans=ta-tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
                    ans=tb-ta; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
                    ans=ta*tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);
                    if(tb.fz!=0){ans=ta/tb; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);}
                    if(ta.fz!=0){ans=tb/ta; if(ans==tg) flag=true,dp[s].clear(); dp[s].insert(ans);}
                    if(flag) break;
                }
                if(flag) break;
            }
        }
        if(flag) cnt++;
    }
    printf("%d\n",cnt);
}
/*
10
1 2 3 4 5 6 7 8 9 10
891
*/

B.集合

分两种情况:

  1. 如果两点穿过圆,那么两点做切线,切线长度加上切点在圆上的距离。
  2. 如果两点不穿过圆,那么要求两点在圆上的反射点。因为反射角等于入射角,就在两点与圆心连接的范围内二分角度即可。

剩下的就是板子的事了。我在写的时候写了个判断条件,如果圆心到两点组成的线段距离等于半径就直接输出两点之间距离,这样一直wa。然后改成如果圆心到其中一点距离等于半径就输出两点之间距离,这样就过了,也不知道为什么。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const db pi=acos(-1);
const db eps=1e-8;
int sign(db k){if(k>eps) return 1; if(k<-eps) return -1; return 0;}
int cmp(db k1,db k2){return sign(k1-k2);}
struct point{
    db x,y;
    point operator + (const point o){return (point){x+o.x,y+o.y};}
    point operator - (const point o){return (point){x-o.x,y-o.y};}
    point operator * (const db o){return (point){x*o,y*o};}
    point operator / (const db o){return (point){x/o,y/o};}
    point trun(db k){return (point){x*cos(k)-y*sin(k),x*sin(k)+y*cos(k)};}
    db abs(){return sqrt(x*x+y*y);}
    db abs2(){return x*x+y*y;}
}a,b;
struct circle{
    point o;db r;
}c;
db dot(point k1,point k2){return k1.x*k2.x+k1.y*k2.y;}
db cross(point k1,point k2){return k1.x*k2.y-k1.y*k2.x;}
point proj(point k1,point k2,point p){
    point k=k2-k1;return k1+k*dot(p-k1,k)/k.abs2();
}
db disPP(point p1,point p2){return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));}
db getAngle(point k1,point k2){return fabs(atan2(cross(k1,k2),dot(k1,k2)));}
point getPC(point p,circle c){return c.o+(p-c.o)*(c.r/disPP(c.o,p));}
bool inmid(db k1,db k2,db k3){return sign(k1-k3)*sign(k2-k3)<=0;}
bool inmid(point p1,point p2,point p3){return inmid(p1.x,p2.x,p3.x)&&inmid(p1.y,p2.y,p3.y);}
db disSP(point k1,point k2,point p){
    point k3=proj(k1,k2,p);
    if(inmid(k1,k2,k3)) return disPP(k3,p);
    return min(disPP(k1,p),disPP(k2,p));
}
int clockwise(point k1,point k2,point k3){
    return sign(cross(k2-k1,k3-k1));
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lf%lf",&a.x,&a.y);
        scanf("%lf%lf",&b.x,&b.y);
        scanf("%lf%lf%lf",&c.o.x,&c.o.y,&c.r);
        db cp=cmp(disSP(a,b,c.o),c.r);
        //if(cp==0) printf("%.3f\n",disPP(a,b));
        if(cp>=0){
            if(cmp(disPP(a,c.o),c.r)==0||cmp(disPP(b,c.o),c.r)==0){
                printf("%.3f\n",disPP(a,b));
                continue;
            }
            db lo=0,hi=getAngle(a-c.o,b-c.o);
            if(clockwise(c.o,a,b)==-1) swap(a,b);
            point base=(a-c.o)*c.r/disPP(a,c.o);
            while(1){
                db mid=(lo+hi)/2;
                point mp=(base).trun(mid)+c.o;
                int tmp=cmp(getAngle(a-mp,mp-c.o),getAngle(b-mp,mp-c.o));
                if(tmp==0){
                    printf("%.3f\n",disPP(a,mp)+disPP(b,mp));
                    break;
                }
                if(tmp>0) hi=mid;
                else lo=mid;
            }
        }
        else{
            db d1=disPP(a,c.o),d2=disPP(b,c.o);
            db angle=getAngle(a-c.o,b-c.o);
            angle-=acos(c.r/d1)+acos(c.r/d2);
            db ans=sqrt(d1*d1-c.r*c.r)+sqrt(d2*d2-c.r*c.r)+angle*c.r;
            printf("%.3f\n",ans);
        }
    }
}

D.精简改良

同样是状压,dp[s][u]表示状态为s,以u为根的答案。这种状态就意味着s里面除了u,其他点不会再连出去边了。

然后利用树形dp的思想更新,枚举子区间suu为根,另一个子区间sv就是s-su,枚举sv的根v,更新方程如下:

dp[s][u]=max(dp[s][u],dp[su][u]+dp[sv][v]+1ll*num[sv]*(n-num[sv])*mp[u][v]);

会有点卡常,最好预处理出状态的每个点。还有对于非法状态就不要更新了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+7e3;
ll dp[N][20];
int num[N];
int n,m;
int u,v,w;
vector<int>pnt[N];
bool flag[N];
int mp[20][20];
int main()
{
    scanf("%d%d",&n,&m);
    for(int s=1;s<(1<<n);s++){
        num[s]=num[s&(s-1)]+1;
        for(int i=1;i<=n;i++){
            if(s&(1<<(i-1))){
                if(num[s]==1) dp[s][i]=0;
                else dp[s][i]=-1;
                pnt[s].push_back(i);
            }
        }
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        mp[u][v]=mp[v][u]=w;
    }
    for(int s=1;s<(1<<n);s++){
        for(int su=(s-1)&s;su;su=(su-1)&s){
            int sv=s-su;
            int sz_u=pnt[su].size();
            int sz_v=pnt[sv].size();
            bool flag=true;
            for(int iu=0;iu<sz_u;iu++){
                int u=pnt[su][iu];
                if(dp[su][u]==-1) break;
                for(int iv=0;iv<sz_v;iv++){
                    int v=pnt[sv][iv];
                    if(dp[sv][v]==-1){flag=false;break;}
                    if(mp[u][v]==0) continue;
                    dp[s][u]=max(dp[s][u],dp[su][u]+dp[sv][v]+1ll*num[sv]*(n-num[sv])*mp[u][v]);
                }
                if(flag==false) break;
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,dp[(1<<n)-1][i]);
    }
    printf("%lld\n",ans);
}

F.小清新数论*

莫比乌斯,两个分块,复杂度为O(N)。

\sum _{i=1}^{N}\sum_{j=1}^{N}u(gcd(i,j))\rightarrow  \sum_{d=1}^{N}u(d)\sum _{i=1}^{N}\sum_{j=1}^{N}[gcd(i,j)==d]\rightarrow  \sum_{d=1}^{N}u(d) \sum _{i=1}^{\left \lfloor \frac{N}{d} \right \rfloor} \sum _{j=1}^{\left \lfloor \frac{N}{d} \right \rfloor}[gcd(i,j)==1] \rightarrow  \sum_{d=1}^{N}u(d) \sum _{i=1}^{\left \lfloor \frac{N}{d} \right \rfloor} u(i)(\frac{N}{di})^{2}

里外两层都有\frac{N}{d},所以都可以分块。队友代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int  N = 1e7+9;
const LL mod = 998244353;
LL u[N];
LL pri[N>>1],now;
bool vis[N];
void init(){
    vis[1]=1;u[1]=1;
    for(int i=2;i<N;i++){
        if(!vis[i])pri[++now]=i,u[i]=-1;
        for(int j=1;j<=now&&pri[j]*i<N;j++){
            vis[pri[j]*i]=1;
            u[pri[j]*i]=-u[i];
            if(i%pri[j]==0){u[pri[j]*i]=0;break;}
        }
    }
    for(int i=1;i<N;i++)u[i]=u[i]+u[i-1];
}

LL solve(LL n){
    LL ans=0;
    LL NN=n;
    for(LL l=1,r;l<=NN;l=r+1){
        r=(n/(n/l));
        LL re=(u[r]-u[l-1])*(n/l)%mod*(n/l)%mod;
        ans=(ans+re)%mod;
    }
    return (ans+mod)%mod;
}

int main(){
    init();
    LL n;
    while(cin>>n){
        LL NN=n;
        LL ans=0;
        for(LL l=1,r;l<=NN;l=r+1){
            r=NN/(NN/l);
            LL d=(l+r)*(r-l+1)/2%mod;
            d=(u[r]-u[l-1]+mod)%mod;
            LL re=solve(n/l);
            //printf("re:%lld\n",re);
            re=(re+mod)%mod;
            ans=(ans+d*re%mod)%mod;
            //printf("ans:%lld\n",ans);
        }
        printf("%lld\n",ans);
    }
}

G.排列

模拟,队友代码

#include <iostream> 
#include<algorithm>
#include<stdio.h>
#include<vector>
#include<map>
using namespace std;
int main(){
    int a[100005];
    int b[100005];
    int c[100005];
    int n;
    while(~scanf("%d",&n))
    {
    int last=1e9;
    int cnt=0;
    int x;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        if(x<last)a[x]=++cnt;
        else a[x]=cnt;
        last=x;
    }
    last=0;
     for(int i=1;i<=n;i++)
     {
         if(a[i]!=last)b[i]=a[i];
         else b[i]=++cnt;
         last=a[i];
     }
     for(int i=1;i<=n;i++)printf("%d ",b[i]);
     printf("\n");
    }
}

H.涂鸦*

每一个坐标为(i,j)的点被染成黑色的概率是\frac{2*(j-l_i+1)*(r_i-j+1)}{(r_i-l_i+1)*(r_i-l_i+2)},所以只要把在矩形外的所有概率累加即可。

对于染白操作可以用差分约束维护,矩形的两个对角线分别打上+1和-1的标记,对于前缀和为0的点就一定在矩形之外,也就对答案有贡献。

#include<iostream>
#include<stdio.h>
using namespace std;
typedef long long ll;
const ll N=1e3+7;
const ll mod=998244353;
ll a[N][N],c[N][N],f[N];
ll qpow(ll x,ll y){
    ll res=1;
    while(y){
        if(y&1) res=res*x%mod;
        x=x*x%mod;y>>=1;
    }
    return res;
}
int main()
{
    ll n,m,q;
    scanf("%lld%lld%lld",&n,&m,&q);
    for(ll i=1,l,r;i<=n;i++){
        scanf("%lld%lld",&l,&r);
        for(ll j=1;j<=m;j++){
            if(j>=l&&j<=r){
                a[i][j]=(a[i][j]+(j-l+1)*(r-j+1)%mod*2%mod*qpow((r-l+1)*(r-l+2)%mod,mod-2)%mod)%mod;
            }
        }
    }
    for(ll i=1,lx,ly,rx,ry;i<=q;i++){
        scanf("%lld%lld%lld%lld",&lx,&ly,&rx,&ry);
        c[lx][ly]++;
        c[rx+1][ry+1]++;
        c[rx+1][ly]--;
        c[lx][ry+1]--;
    }
    ll ans=0;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=n;j++){
            c[i][j]+=c[i-1][j]+c[i][j-1]-c[i-1][j-1];
            if(c[i][j]==0){
                ans=(ans+a[i][j])%mod;
            }
        }
    }
    printf("%lld\n",ans);
}

I.石头剪刀布

因为题目保证了数据的合法性,也就是说不存在答案为0的情况。所以如果已知每个人的主场次数u和客场次数v,答案就会变成3^{n}\ast \frac{2}{3}^{u}\ast \frac{1}{3}^{v}

画一下图我们可以发现这是一张树,我们对这个可以用并查集来维护。维护两个值:总数a,主场次数b。假设有操作1 x y,就把y连向x,并做如下两个更新操作。

  1. a[y]-=a[x],a[x]++;
  2. b[x]++,b[y]-=b[x];

询问的时候直接对节点到根这条路径上的值进行累加。

可以对并查集按秩合并,这样既要考虑两种合并的不同更新。个人认为也可以直接路径压缩,压缩的点只要减去新的根节点的值就行了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=998244353;
const ll N=2e5+7;
ll fa[N],a[N],b[N],rk[N];
ll n,m,k,x,y;
ll qpow(ll x,ll y){
    ll res=1;
    while(y){
        if(y&1) res=x*res%mod;
        x=x*x%mod,y>>=1;
    }
    return res;
}
struct Node{
    ll fa,aa,bb;
};
Node fd(ll x){
    if(x==fa[x]) return (Node){x,a[x],b[x]};
    Node tmp=fd(fa[x]);
    Node res=tmp;
    res.aa+=a[x];res.bb+=b[x];
    fa[x]=tmp.fa;
    a[x]+=tmp.aa-a[tmp.fa];
    b[x]+=tmp.bb-b[tmp.fa];
    return res;
}
ll un(ll x,ll y){
    x=fd(x).fa,y=fd(y).fa;
    fa[y]=x;
    a[y]-=a[x],a[x]++;
    b[x]++,b[y]-=b[x];
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++) rk[i]=1,fa[i]=i;
    for(ll i=1;i<=m;i++){
        scanf("%lld",&k);
        if(k==1){
            scanf("%lld%lld",&x,&y);
            un(x,y);
        }
        else{
            scanf("%lld",&x);
            Node tmp=fd(x);
            ll ans=qpow(3,n)*qpow(2,tmp.bb)%mod*qpow(qpow(3,tmp.aa),mod-2)%mod;
            printf("%lld\n",ans);
        }
    }
}


   

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值