AtCoder Regular Contest 111补题报告(A数学 B思维(匹配问题) C思维(贪心)D思维(保证有解时猜测)E数学(类欧几里得算法))

A - Simple Math 2

推式子:
⌊ 1 0 N M ⌋ m o d M = ⌊ 1 0 N M ⌋ − k M = ⌊ 1 0 N − k M 2 M ⌋ ⌊{10^N\over M}⌋modM=⌊{10^N\over M}⌋-kM=⌊{10^N-kM^2\over M}⌋ M10NmodM=M10NkM=M10NkM2
所以,我们快速幂取模求 1 0 n m o d    m 2 10^n \mod m^2 10nmodm2后再整除M即可。

ll ksm(ll a,ll n,ll p){
    ll res=1;
    while(n){
        if(n&1)res=res*a%p;
        a=a*a%p;
        n>>=1;
    }
    return (res%p+p)%p;
}
ll n,m;

int main(){
    read(n),read(m);
    printf("%lld\n",(ksm(10,n,m*m)/m+m)%m);
    return 0;
}

B - Reversible Cards

和多校的题一样:https://ac.nowcoder.com/acm/problem/210022
对每一对a、b,我们可以看作有一条边连接着a、b,则输入就连为一个图,意义为:对于一个连通分支,每条边表示一次选择,每次可以选择这条边的一个端点。由此可得,对于一个连通分量,设若内部成环,则整个分量内的节点都可以选。否则,顶多选择总量-1个。据此并查集维护即可。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
typedef long long ll;
template<typename T>
void read(T&x){
    ll f=1;
    x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    x*=f;
}
//===========================================================
#define int ll
const int maxn=200000+100;
const int MAXN=400000+100;
int s[MAXN],du[MAXN];
int a[MAXN],b[MAXN];
int cnt[MAXN];
int n;
int find(int x){
    return x==s[x]?x:s[x]=find(s[x]);
}

void merge(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y)return ;
    s[x]=y;
}
set<int>ss;
set<int>S;

signed main(){
    //freopen("in.txt","r",stdin);
    rep(i,0,MAXN-1)s[i]=i;
    read(n);
    rep(i,1,n)read(a[i]),read(b[i]);
    rep(i,1,n){
        //cerr<<a[i]<<" "<<b[i]<<endl;
        du[a[i]]++;
        du[b[i]]++;
        merge(a[i],b[i]);
        S.insert(a[i]);
        S.insert(b[i]);
    }
    for(auto i:S){
        int fa=find(i);
        ss.insert(fa);
        cnt[fa]++;
        if(i==fa)continue;
        du[fa]+=du[i];
    }
    int ans=0;
    for(auto fa:ss){
        //cerr<<fa<<" "<<du[fa]<<" "<<cnt[fa]<<endl;
        if(du[fa]/2==(cnt[fa]-1)){
            ans+=cnt[fa]-1;
        }
        else{
            ans+=cnt[fa];
        }
    }
    printf("%lld\n",ans);
    return 0;
}

C - Too Heavy

题意需要理解下。。
这也是一道通过图比较好找出思路的题。我们将1~n构建为一个有向图,构建方式为:
i->p[i]->p[p[i]]->……i。因为p为1~n的排列,所以一定存在回路。
对于其中一个连通分量,若其中存在一个点j,满足a[j]<=b[p[j]],也就是现在j身上的背包没法进行操作,则这个任务无法完成,直接-1。
接下来是如何构造的问题。我们找出这个连通分量中的一个点mxp,使得a[mxp]的这个连通分量中最大的,可得mxp这个人可以拿得起这个连通分量内的所有背包。则可以通过以下方式构造:
我们先要明确,对于这个回路中的任意一个点i,i身上的背包是要给i+1的,特殊的,n要给1。
对于i>mxp,因为mxp这个人可以拿所有背包,所以,我们以mxp为中介,mxp<一>i(建议手跑下)。
再对于i<mxp,mxp<一>i
经过这一过程,我们就可以完成交换。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
typedef long long ll;
template<typename T>
void read(T&x){
    ll f=1;
    x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    x*=f;
}
//=========================================
#define pii pair<int,int>
vector<pii>ans;
const int maxn=200000+100;
int a[maxn],b[maxn],p[maxn];
bitset<maxn>vis;
int n;
bool solve(){
    rep(i,1,n){
        if(!vis[i]){
            vis[i]=1;
            vector<int>cir;
            int ptr=i;
            cir.push_back(ptr);
            ptr=p[i];
            while(ptr!=i){
                vis[ptr]=1;
                cir.push_back(ptr);
                ptr=p[ptr];
            }
            if(cir.size()==1)continue;
            for(auto x:cir){
                if(a[x]<=b[p[x]])return false;
            }
            int mxp=0;
            for(int i=1;i<cir.size();i++){
                if(a[cir[i]]>a[cir[mxp]])mxp=i;
            }
            for(int i=mxp+1;i<cir.size();++i){
                ans.push_back(make_pair(cir[i],cir[mxp]));
            }
            for(int i=0;i<mxp;++i){
                ans.push_back(make_pair(cir[i],cir[mxp]));
            }
        }
    }
    return true;
}

int main(){
    //freopen("in.txt","r",stdin);
    read(n);
    rep(i,1,n)read(a[i]);
    rep(i,1,n)read(b[i]);
    rep(i,1,n)read(p[i]);
    if(!solve())puts("-1");
    else{
        printf("%d\n",ans.size());
        for(auto x:ans){
            printf("%d %d\n",x.first,x.second);
        }
    }
    return 0;
}

D - Orientation

题目保证数据一定有解,那么,我们其实就不用想太多了。对于有联通的i、j两点,我们只需要想到:
若c[i]>c[j],则i->j
若c[i]<c[j],则i<-j
那等于的时候呢?我们可以想到它应该是连成了一个环才会有这种情况发生,否则无解。那么,我们只需要沿着c和它相等、且相连的节点走个环即可。
注意,存在路径两端c相等不代表整个连通分量都需要在环上(我卡在了这里)。可以有独立的几个点是全部向外指出的,然后从环上走不到这几个点上。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
typedef long long ll;
template<typename T>
void read(T&x){
    x=0;
    ll f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    x*=f;
}
//=====================================================
const int maxn=205;
int n,m;
struct Edge{
    int to,next,id;
}e[maxn*maxn*2];
int head[maxn],cnt;
int mark[maxn][maxn];
int c[maxn];
int ans[maxn*maxn*2];
struct Da{
    int u,v,num;
};
vector<Da>save;
void add(int x,int y,int z){
    e[cnt].to=y;
    e[cnt].next=head[x];
    e[cnt].id=z;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
bool vis[maxn];

void dfs(int u){
    vis[u]=1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        if(ans[e[i].id]==0){
            ans[e[i].id]=(mark[u][v]?1:-1);
            if(!vis[v])dfs(v);
        }
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    memset(head,-1,sizeof(head));
    read(n),read(m);
    rep(i,1,m){
        int x,y;
        read(x),read(y);
        save.push_back({x,y,i});
        mark[x][y]=1;
    }
    rep(i,1,n)read(c[i]);
    for(int i=0;i<save.size();i++){
        auto x=save[i];
        int u=x.u;
        int v=x.v;
        if(vis[u]&&vis[v])continue;
        if(c[u]>c[v])ans[x.num]=1;
        else if(c[u]<c[v])ans[x.num]=-1;
        else{
            add(u,v,i+1);
            add(v,u,i+1);
        }
    }
    rep(i,1,n){
        if(!vis[i]){
            dfs(i);
        }
    }
    rep(i,1,m){
        if(ans[i]==1)puts("->");
        else if(ans[i]==-1)puts("<-");
        else exit(-1);
    }
    return 0;
}

E - Simple Math 3

关于类欧几里得算法,我也不是很懂,贴板子吧~

洛谷模板题:

const int mod = 998244353;
#define i2 499122177ll
#define i6 166374059ll
struct node{
    int f, g, h;
    node() { f = g = h = 0; }
};
node cal(int n, int a, int b, int c){
    int ac = a / c, bc = b / c, m = (a * n + b) / c, n1 = n + 1, n21 = n * 2 + 1;
    node d;
    if (a == 0){
        d.f = bc * n1 % mod;
        d.g = bc * n % mod * n1 % mod * i2 % mod;
        d.h = bc * bc % mod * n1 % mod;
        return d;
    }
    if (a >= c || b >= c){
        d.f = n * n1 % mod * i2 % mod * ac % mod + bc * n1 % mod;
        d.g = ac * n % mod * n1 % mod * n21 % mod * i6 % mod + bc * n % mod * n1 % mod * i2 % mod;
        d.h = ac * ac % mod * n % mod * n1 % mod * n21 % mod * i6 % mod +
              bc * bc % mod * n1 % mod + ac * bc % mod * n % mod * n1 % mod;
        d.f %= mod, d.g %= mod, d.h %= mod;

        node e = cal(n, a % c, b % c, c);

        d.h += e.h + 2 * bc % mod * e.f % mod + 2 * ac % mod * e.g % mod;
        d.g += e.g, d.f += e.f;
        d.f %= mod, d.g %= mod, d.h %= mod;
        return d;
    }
    else{
        node e = cal(m - 1, c, c - b - 1, a);
        d.f = n * m % mod - e.f, d.f = (d.f % mod + mod) % mod;
        d.g = m * n % mod * n1 % mod - e.h - e.f, d.g = (d.g * i2 % mod + mod) % mod;
        d.h = n * m % mod * (m + 1) % mod - 2 * e.g - 2 * e.f - d.f;
        d.h = (d.h % mod + mod) % mod;
        return d;
    }
}

若只需要求f

ll cal(ll a,ll b,ll c,ll n){
    if(!a){
        return (n+1)*(b/c);
    }
    if(a>=c||b>=c){
        return n*(n+1)/2*(a/c)+(n+1)*(b/c)+cal(a%c,b%c,c,n);
    }
    ll m=(a*n+b)/c;
    return n*m-cal(c,c-b-1,a,m-1);
}

这两个板子本质上是一样的,只是第一个板子有取模(我懒得去掉 ),第二个板子只能够求f,第一个还能求g和h(详见oiwiki或洛谷)
回到这道题。题目意思就是要我们求有几个区间 [ A + B i , A + C i ] [{A+Bi},{A+Ci}] [A+Bi,A+Ci]内无d的倍数。
首先,根据鸽巢原理,我们可以知道一个长度为D的区间,一定会存在一点i,使得i mod d=0,所以,我们对区间的要求为: ( A + C i ) − ( A + B i ) + 1 ≤ D − 1 (A+Ci)-(A+Bi)+1 \le D-1 (A+Ci)(A+Bi)+1D1。变形为: i ≤ D − 2 C − B i\le{D-2\over C-B} iCBD2,那么,我们i的备选答案就有 k = D − 2 C − B k={D-2\over C-B} k=CBD2个。
但是这些区间并不是全都符合要求的,只是区间长度符合了要求,区间内不一定符合。对于一个符合的区间,我们其实可以联想到 l − 1 d = r d {l-1\over d}={r \over d} dl1=dr,因为我们知道,1~n内d的倍数有 n d n\over d dn个,那么,一个区间内没有d的倍数,根据前缀和的思想,其实就是左右边界的d的倍数个数前缀和相等。
那么,对于备选的那些i,我们上面有保证了 区 间 长 度 ≤ d − 1 区间长度\le d-1 d1,所以,我们可以这样统计那些不符合的区间:
∑ i = 1 k [ A + C i D ≠ A + B i − 1 D ] = ∑ i = 1 k [ A + C i D − A + B i − 1 D ] \sum_{i=1}^k[{A+Ci\over D}\neq{A+Bi-1\over D}]=\sum_{i=1}^k[{A+Ci\over D}-{A+Bi-1\over D}] i=1k[DA+Ci=DA+Bi1]=i=1k[DA+CiDA+Bi1]
综上:我们的答案就是 k − ∑ i = 1 k [ A + C i D − A + B i − 1 D ] k-\sum_{i=1}^k[{A+Ci\over D}-{A+Bi-1\over D}] ki=1k[DA+CiDA+Bi1],对于后面的式子,我们可以用类欧几里得求。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>
void read(T&x){
    ll f=1;
    x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    x*=f;
}
//=======================================================
#define int ll
int t,a,b,c,d;
ll cal(ll a,ll b,ll c,ll n){
    if(!a){
        return (n+1)*(b/c);
    }
    if(a>=c||b>=c){
        return n*(n+1)/2*(a/c)+(n+1)*(b/c)+cal(a%c,b%c,c,n);
    }
    ll m=(a*n+b)/c;
    return n*m-cal(c,c-b-1,a,m-1);
}
ll solve(){
    ll k=(d-2)/(c-b);
    auto p1=cal(c,a,d,k);
    auto p2=cal(b,a-1,d,k);
    return k-p1+p2+a/d-(a-1)/d;
}

signed main(){
    //freopen("in.txt","r",stdin);
    read(t);
    while(t--){
        read(a),read(b),read(c),read(d);
        printf("%lld\n",solve());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值