AtCoder Grand Contest 038题解

好久没更了
写点东西吧= =

A 01Matrix

简单构造

左上角和右下角染成1其他染成0即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[1010][1010];
int main(){
    int n,m,A,B;
    cin>>n>>m>>A>>B;
    if(A*2>m||B*2>n)return puts("-1"),0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        if(i<=B&&j<=A)putchar('1');
        else if(i>B&&j>A)putchar('1');
        else putchar('0');
        if(j==m)puts("");
    }
    return 0;
}
B Sorting a Segment

考虑若(L,R)和(L',R')排序后相同

则 $ \forall $ L<L''<L',(L,R)和(L'',R'')排序后相同

因此只需处理相邻两个是否排序后是否相同即左右两端点是否恰是最值即可

注意若两个区间排序后不改变任何数位置需要特殊处理

形式化的,我们先拉出所有排序后不改变位置的区间然后数前面相邻相同的数量即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[200010],b[2000010],n,m;
int qzl[200010],qzr[200010],hzl[200010],hzr[200010]; 
int calc(int L,int R){
    return a[L]==min(hzl[L],qzl[R])&&a[R]==max(hzr[L],qzr[R]);
}
int main(){
    scanf("%d%d",&n,&m);
    int tag=-1;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        if(i>0&&a[i]>a[i-1])b[i]=b[i-1]+1;
        else b[i]=1;
        if(b[i]>=m)tag++;
    }
    for(int i=0;i<n;i++){
        if(i%m==0)qzl[i]=qzr[i]=a[i];
        else qzl[i]=min(qzl[i-1],a[i]),qzr[i]=max(qzr[i-1],a[i]);
    }
    for(int i=n-1;i>=0;i--){
        if(i==n-1||(i+1)%m==0)hzl[i]=hzr[i]=a[i];
        else hzl[i]=min(hzl[i+1],a[i]),hzr[i]=max(hzr[i+1],a[i]);
    }
    int ans=n-m+1;
    for(int i=m-1;i<n-1;i++)
    if(b[i]<m&&b[i+1]<m&&calc(i-m+1,i+1))ans--;
    ans-=max(0,tag);cout<<ans;
    return 0;
}
C LCMs

简单数论题

\[ \begin{aligned} &\sum_{i=1}^n\sum_{j=i+1}^n\frac{a_ia_j}{\gcd(a_i,a_j)}\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j[\gcd(a_i,a_j)=1]\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j\sum_{k|a_i,k|a_j}\mu(k)\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j\sum_{k|a_i,k|a_j}\mu(k)\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{k=1}^{\frac{max}{d}}\mu(k)(\sum_{i=1}^n[dk|a_i]\sum_{j=i+1}^n[dk|a_j]a_ia_j)\\ \end{aligned} \]

右边部分显然是关于dk的一个函数 预处理就做完了

#include<bits/stdc++.h>
#define ll long long
#define p 998244353
#define inv2 499122177
using namespace std;
int mu[1000010],ss[1000010],cnt;bool pri[1000010];
int a[200010],tong[1000010];
int h[1000010],f[1000010];
int n,m,k;
int inv[1000010];
void init(int N){
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(!pri[i])ss[++cnt]=i,mu[i]=-1;
        for(int j=1;i*ss[j]<=N&&j<=cnt;j++){
            pri[i*ss[j]]=1;
            if(i%ss[j]==0){
                mu[i*ss[j]]=0;
                break;
            }
            mu[i*ss[j]]=-mu[i];
        }
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),tong[a[i]]++;
    inv[1]=1;
    for(int i=2;i<=1000000;i++)inv[i]=1ll*inv[p%i]*(p-p/i)%p;
    init(1000000);
    for(int i=1;i<=1000000;i++){
        for(int j=i;j<=1000000;j+=i)(h[i]+=1ll*tong[j]*j%p)%=p,
        (f[i]+=1ll*j*tong[j]%p*j%p)%=p;
    }
    for(int i=1;i<=1000000;i++)
    f[i]=1ll*((1ll*h[i]*h[i]%p-f[i])%p+p)%p*inv2%p;
    ll ans=0;
    for(int k=1;k<=1000000;k++)
    for(int T=k;T<=1000000;T+=k)
    (ans+=1ll*inv[k]*mu[T/k]%p*f[T]%p)%=p;cout<<ans;
    return 0;
}
D Unique Path

先考虑所有"只能有一条路径"的对,这两个点显然在同一联通块中

且所有联通块不能出现环 即类似克鲁斯卡尔的加边

然后再考虑所有"有多条路径"的对,如果这个点对在同一联通块内则GG

因为这会导致连通块内出现环即导致"只能有一条路径"的对GG

然后发现任意两个联通块之间只可能有一条边,否则会出现环

计算m是否超过能插入的最大边数即可

#include<bits/stdc++.h>
int n,k,a,b,c,ds,fa[200010],L[200010],R[200010],d;
int ask(int x){return x==fa[x]?x:fa[x]=ask(fa[x]);}
long long m;
int main(){
    scanf("%d%lld%d",&n,&m,&k);ds=n;
    for(int i=0;i<n;i++)fa[i]=i;
    while(k--){
        scanf("%d%d%d",&a,&b,&c);
        int p=ask(a),q=ask(b);
        if(c==0&&p!=q)ds--,fa[q]=p;
        if(c==1)L[++d]=a,R[d]=b;
    }
    for(int i=1;i<=d;i++)if(ask(L[i])==ask(R[i]))return puts("No"),0;
    puts((m+ds-n>1ll*ds*(ds-1)/2)||(d&&(ds==2||m<n))?"No":"Yes");
    return 0;
}
E Gachapon

神仙题

考虑minmax反演,转换为了求每个子集的“存在一个元素 $ i $ 出现次数超过 $ b_i $” 期望次数

期望次数等价于所有“不存在一个元素 $ i $ 出现次数超过 $ b_i $” 的状态的期望出现次数之和

因为假如某个操作序列经过q次走出去了,前q个前置状态都相当于没走出去

而每个没走出去的状态的出现次数等价于出现概率乘上 $ \frac{S}{U} $ 其中U是当前子集的a的总和

这是因为到达该状态之后只要接下来选中的元素在集合外都会使得出现次数++

等比数列收敛后可得到上述结论

接下来我们只需要计算每个状态的出现概率 这样我们可以暂时忽略走到子集外的情况

我们枚举每个子集,考虑枚举操作序列的长度(不超过 $ \sum b_i $ )并计算这样的操作序列的数量

容易发现这可以由指数生成函数 $ E_d(x)=\sum\limits_{i=0}^{B_d-1}\frac{x^i(\frac{A_d}{U})^i}{i!}$ 代替

其中 $ x^i $ 的系数就是操作序列长度为 $ i$ 的答案

枚举子集复杂度显然GG 我们注意到我们没有必要枚举子集,只需要枚举U即可

然后做类似背包的DP,每次转移进行多项式乘法(这里可以暴力乘)

就做完了... 感觉讲的很不清楚啊... 还有问题的话私信我吧...(没有语言表达能力)

#include<bits/stdc++.h>
#define ll long long
#define p 998244353
using namespace std;
int a[1010],b[1010];
int jc[1010],njc[1010],inv[1010];
vector<int>ret,E[1010],all[1010];
vector<int>mul(vector<int>x,vector<int>y){
    vector<int>ret;
    int n=x.size(),m=y.size();
    ret.resize(n+m-1);
    for(int i=0;i<n;i++)
    for(int j=0;j<m;j++)
    (ret[i+j]+=1ll*x[i]*y[j]%p)%=p;return ret;
}
void del(vector<int>&x,vector<int>y){
    int n=x.size(),m=y.size();
    x.resize(max(n,m));
    for(int i=0;i<m;i++)(x[i]-=y[i])%=p; 
}
void add(vector<int>&x,vector<int>y){
    int n=x.size(),m=y.size();
    x.resize(max(n,m));
    for(int i=0;i<m;i++)(x[i]+=y[i])%=p; 
}
int main(){
    int n;scanf("%d",&n);
    for(int i=0;i<2;i++)jc[i]=njc[i]=inv[i]=1;
    for(int i=2;i<=405;i++){
        jc[i]=1ll*jc[i-1]*i%p;
        inv[i]=1ll*inv[p%i]*(p-p/i)%p;
        njc[i]=1ll*njc[i-1]*inv[i]%p;
    }
    for(int i=0;i<n;i++)scanf("%d %d",&a[i],&b[i]); 
    int S=0,ans=0;
    for(int i=0;i<n;i++)S+=a[i];
    for(int i=0;i<n;i++){
        for(int k=0,P=1;k<b[i];k++,P=1ll*P*a[i]%p)E[i].push_back(1ll*P*njc[k]%p);
    }
    for(int i=0;i<n;i++){
        for(int k=S-a[i];k>=1;k--)if(all[k].size())
        del(all[a[i]+k],mul(E[i],all[k]));
        add(all[a[i]],E[i]);
    }
    for(int i=1;i<=S;i++){
        for(int j=0,u=inv[i];j<all[i].size();j++,u=1ll*u*inv[i]%p)
        (ans+=1ll*all[i][j]*u%p*jc[j]%p)%=p;
    }
    (ans+=p)%=p;
    cout<<1ll*ans*S%p;
    return 0;
}
F Two Permutations

比E清真多了...

考虑两个排列的每个环上,所有点要么一起动要么一起不动

因此就变成了以下问题:

  • 每个环可以选择动(1)或不动(0)
  • 两个环若一起动或一起不动会有一个代价
  • 某个环若动或不动会有一个代价
  • 要求代价最小值

这是经典的二元关系最小割 这里有

注意一点细节如把第二个序列改成动(0)或不动(1)这样就是两个环若不同则有代价

注意这张图是二分图 直接dinic就好了 复杂度是$ O(n \sqrt{n})$的

ISAP好像过不去..?

#include<bits/stdc++.h>
#define ll long long
#define M 400010
using namespace std;
int n,k,cnt,S,T,h,t;
int F[M],L[M],N[M],a[M],c[M],Gap[M],dis[M],q[M],cur[M];
void add(int x,int y,int z,int fla=0){
    a[++k]=y;c[k]=z;
    N[k]=F[x];F[x]=k;
    if(!fla)add(y,x,0,1);
}
bool BFS(int x,int y){
    q[t=1]=x;h=0;
    for(int i=1;i<=n;i++)cur[i]=F[i],dis[i]=1000000000;
    dis[x]=0;
    while(h<t){
        x=q[++h];
        for(int i=F[x];i;i=N[i])if(c[i]&&dis[a[i]]>n){
            dis[a[i]]=dis[x]+1;
            if(a[i]==y)return 1;
            q[++t]=a[i];
        }
    }
    return dis[y]<=n;
}
int dfs(int x,int T,int flow){
    if(x==T)return flow;
    int used=0;
    for(int i=cur[x];i;i=cur[x]=N[i])if(c[i]&&dis[a[i]]==dis[x]+1){
        const int v=dfs(a[i],T,min(flow-used,c[i]));
        if(!v)continue;c[i]-=v;c[i^1]+=v;used+=v;
        if(used>=flow)return used;
    }
    return used;
}
int dinic(int S,int T){
    int ans=0;
    while(BFS(S,T))ans+=dfs(S,T,1000000000);
    return ans;
}
int A[200010],B[200010],P[200010],Q[200010],cnt1;
int main(){
    scanf("%d",&n);k=1;
    for(int i=1;i<=n;i++)scanf("%d",&A[i]),A[i]++;
    for(int i=1;i<=n;i++)scanf("%d",&B[i]),B[i]++;
    for(int i=1;i<=n;i++)if(!P[i]){
        P[i]=++cnt1;
        for(int x=A[i];!P[x];x=A[x])P[x]=cnt1;
    }
    for(int i=1;i<=n;i++)if(!Q[i]){
        Q[i]=++cnt1;
        for(int x=B[i];!Q[x];x=B[x])Q[x]=cnt1;
    }   
    int all=n;
    S=cnt1+1;T=cnt1+2;
    for(int i=1;i<=n;i++){
        if(A[i]==B[i]){
            if(A[i]==i)all--;
            else add(P[i],Q[i],1),add(Q[i],P[i],1);
        }
        else {
            if(A[i]==i)add(Q[i],T,1);
            else if(B[i]==i)add(S,P[i],1);
            else add(Q[i],P[i],1);
        }
    }
    n=T;
    cout<<all-dinic(S,T);
    return 0;
}

转载于:https://www.cnblogs.com/DreamlessDreams/p/11602891.html

AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值