• AtCoder Grand Contest 038


    Preface

    当AGC039已经结束的时候(然而我在写国庆作文没打),我才回来补AGC038的题解

    这场感觉难度偏简单,但有一道直击我要害的数学题搞了好久还是大雾

    立一个flag:下场AGC一定准时打!


    A - 01 Matrix

    简单构造题。考虑首先满足第一个限制,那么把前(a)列全部填成(1)

    然后第二个限制也很简单,直接把前(b)行取反就好了,容易证明此时即为合法构造

    #include<cstdio>
    #define RI register int
    using namespace std;
    const int N=1005;
    int n,m,a,b; bool p[N][N];
    int main()
    {
    	RI i,j; scanf("%d%d%d%d",&n,&m,&a,&b);
    	for (j=1;j<=a;++j) for (i=1;i<=n;++i) p[i][j]^=1;
    	for (i=1;i<=b;++i) for (j=1;j<=m;++j) p[i][j]^=1;
    	for (i=1;i<=n;++i,putchar('
    ')) for (j=1;j<=m;++j)
    	putchar(p[i][j]+48); return 0;
    }
    
    

    B - Sorting a Segment

    简单讨论题。考虑如何对相同的区间去重我会线段树维护Hash

    首先本身无交的有序的区间之间会构成重复,还有剩下的一种情况(此时两个区间有交):

    设两个区间为([l_1,r_1],[l_2,r_2](l_1<l_2)),那么([l1,l_2))一定是最小的(l_2-l_1)个数升序排列,右边同理

    考虑在相邻的区间之间去重,那么此时([i,i+k-1],[i+1,i+k])等价当且仅当(p_i)([i,i+k-1])中最小的,并且(p_{i+k})([i+1,i+k])中最大的

    此时(i+1)就不用被计算,直接写个单调栈判断一下就好了

    #include<cstdio>
    #include<iostream>
    #define RI register int
    using namespace std;
    const int N=200005;
    int n,k,a[N],q[N],cur,ans; bool ismi[N],ismx[N];
    int main()
    {
    	RI i,j,H,T; scanf("%d%d",&n,&k);
    	for (i=1;i<=n;++i) scanf("%d",&a[i]);
    	for (T=0,q[H=1]=n+1,i=n;i;--i)
    	{
    		while (H<=T&&q[H]-i>=k) ++H; while (H<=T&&a[i]<a[q[T]]) --T;
    		q[++T]=i; if (H==T) ismi[i]=1;
    	}
    	for (T=0,H=i=1;i<=n;++i)
    	{
    		while (H<=T&&i-q[H]>=k) ++H; while (H<=T&&a[i]>a[q[T]]) --T;
    		q[++T]=i; if (H==T) ismx[i]=1;
    	}
    	for (i=1;i<=n;i=j+1)
    	{
    		for (j=i;a[j+1]>a[j];++j); cur+=(j-i+1>=k);
    	}
    	for (ans=n-k+1-max(cur-1,0),i=k+1;i<=n;++i)
    	ans-=ismi[i-k]&&ismx[i]; return printf("%d",ans),0;
    }
    
    

    C - LCMs

    简单数论题。废话不多说来推式子:

    (ans=sum_{i=1}^nsum_{j=1}^n operatorname{lcm}(a_i,a_j)),则最后的答案(=frac{ans-sum_{i=1}^n a_i}{2})

    [ans=sum_{i=1}^nsum_{j=1}^n operatorname{lcm}(a_i,a_j) ]

    [=sum_{d=1}^n dsum_{i=1}^n sum_{j=1}^n[d|a_i][d|a[j]]frac{a_i}{d}frac{a_j}{d}[gcd(frac{a_i}{d},frac{a_j}{d})=1] ]

    [=sum_{d=1}^n dsum_{i=1}^n sum_{j=1}^n[d|a_i][d|a[j]]frac{a_i}{d}frac{a_j}{d}sum_{t|a_i,t|a_j}mu(t) ]

    [=sum_{T=1}^nsum_{d|T} dsum_{i=1}^n sum_{j=1}^n[T|a_i][T|a_j]frac{a_i}{T}frac{a_j}{T} imesfrac{T^2}{d^2}cdot mu(frac{T}{d}) ]

    [=sum_{T=1}^nsum_{i=1}^n sum_{j=1}^n[T|a_i][T|a_j]frac{a_i}{T}frac{a_j}{T} imes Tsum_{d|T} frac{T}{d}cdot mu(frac{T}{d}) ]

    [=sum_{T=1}^nsum_{i=1}^n sum_{j=1}^n[T|a_i][T|a_j]frac{a_i}{T}frac{a_j}{T} imes Tsum_{d|T} dcdot mu(d) ]

    因此我们直接暴力预处理(g(n)=nsum_{d|n} dcdot mu(d)),然后记录一下每个数出现的次数,(O(nlog n))暴算即可

    #include<cstdio>
    #define RI register int
    #define CI const int&
    using namespace std;
    const int N=1000000,mod=998244353;
    int n,x,ans,sum,ct[N+5],prime[N+5],cnt,mu[N+5],g[N+5]; bool vis[N+5];
    inline void inc(int& x,CI y)
    {
    	if ((x+=y)>=mod) x-=mod;
    }
    inline void dec(int& x,CI y)
    {
    	if ((x-=y)<0) x+=mod;
    }
    #define Pj prime[j]
    inline void init(CI n)
    {
    	RI i,j; for (vis[1]=mu[1]=1,i=2;i<=n;++i)
    	{
    		if (!vis[i]) prime[++cnt]=i,mu[i]=-1;
    		for (j=1;j<=cnt&&i*Pj<=n;++j)
    		{
    			vis[i*Pj]=1; if (i%Pj) mu[i*Pj]=-mu[i]; else break;
    		}
    	}
    	for (i=1;i<=n;++i) if (mu[i]) for (j=i;j<=n;j+=i)
    	if (~mu[i]) inc(g[j],i); else dec(g[j],i);
    }
    #undef Pi
    int main()
    {
    	RI i,j; for (init(N),scanf("%d",&n),i=1;i<=n;++i)
    	scanf("%d",&x),++ct[x],inc(sum,x); for (i=1;i<=N;++i)
    	{
    		int ret=0; for (j=i;j<=N;j+=i) inc(ret,1LL*(j/i)*ct[j]%mod);
    		ret=1LL*ret*ret%mod; inc(ans,1LL*ret*i%mod*g[i]%mod);
    	}
    	return dec(ans,sum),printf("%d",1LL*ans*(mod+1>>1)%mod),0;
    }
    
    

    D - Unique Path

    分类讨论题。首先我们把一颗树的情况特判掉,然后再考虑接下来怎么做

    如果我们把每次连(0)边的点缩起来,那么就会得到若干个联通块,再判断一下如果是(1)边那么一定不能在同一个联通块里

    考虑联通块内连成树,然后每个联通块搞一个代表与其它联通块连边

    记联通块个数为(c),那么可以发现它们之间边数的下界是(c),即连成一个

    而边数的上界也很简单,是(frac{c imes(c-1)}{2})即练成一个完全图

    然后看一下给出的边数在不在这个范围内即可

    #include<cstdio>
    #define RI register int
    #define CI const int&
    using namespace std;
    const int N=100005;
    int n,q,a[N],b[N],c[N],cur; long long m;
    class UnionFindSet
    {
    	private:
    		int fa[N];
    	public:
    		inline void init(void)
    		{
    			for (RI i=1;i<=n;++i) fa[i]=i;
    		}
    		inline int getfa(CI x)
    		{
    			return x!=fa[x]?fa[x]=getfa(fa[x]):x;
    		}
    		inline void Union(int x,int y)
    		{
    			x=getfa(x); y=getfa(y); if (x!=y) fa[x]=y;
    		}
    		inline bool identify(CI x,CI y)
    		{
    			return getfa(x)==getfa(y);
    		}
    }S;
    int main()
    {
    	RI i; for (scanf("%d%lld%d",&n,&m,&q),S.init(),i=1;i<=q;++i)
    	{
    		scanf("%d%d%d",&a[i],&b[i],&c[i]); ++a[i]; ++b[i];
    		if (!c[i]) S.Union(a[i],b[i]); if (m==n-1&&c[i]) return puts("No"),0;
    	}
    	if (m==n-1) return puts("Yes"),0; for (i=1;i<=q;++i)
    	if (c[i]&&S.identify(a[i],b[i])) return puts("No"),0;
    	for (i=1;i<=n;++i) cur+=S.getfa(i)==i;
    	return puts(m<=n+(1LL*cur*(cur-3)>>1)?"Yes":"No"),0;
    }
    
    

    E - Gachapon

    神仙期望题。根本不会做,多亏了曲明姐姐的指导

    题解什么的可以去她博客看,大概就是先Min-Max容斥,再变个形,推一个式子再DP

    恩对就是这样(对神仙数学题毫无抵抗能力)

    而且这题第一次写的代码不知道怎么被吃掉了,只好写博客的时候补了一次

    #include<cstdio>
    #define RI int
    #define CI const int&
    using namespace std;
    const int N=405,mod=998244353;
    int n,s1,s2,a[N],b[N],fact[N],inv[N],pw[N],f[N][N][N],ans;
    inline void inc(int& x,CI y)
    {
    	if ((x+=y)>=mod) x-=mod;
    }
    inline void dec(int& x,CI y)
    {
    	if ((x-=y)<0) x+=mod;
    }
    inline int quick_pow(int x,int p=mod-2,int mul=1)
    {
    	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
    }
    inline void init(CI n=400)
    {
    	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
    	for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
    }
    int main()
    {
    	RI i,j,k,p; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]);
    	for (init(),f[0][0][0]=mod-1,i=1;i<=n;++i)
    	{
    		for (j=0;j<=s1;++j) for (k=0;k<=s2;++k) f[i][j][k]=f[i-1][j][k];
    		for (pw[0]=j=1;j<b[i];++j) pw[j]=1LL*pw[j-1]*a[i]%mod;
    		for (j=0;j<b[i];++j) pw[j]=1LL*pw[j]*inv[j]%mod;
    		for (j=0;j<=s1;++j) for (k=0;k<=s2;++k) for (p=0;p<b[i];++p)
    		dec(f[i][j+p][k+a[i]],1LL*f[i-1][j][k]*pw[p]%mod); s1+=b[i]-1; s2+=a[i];		
    	}
    	for (i=0;i<=s1;++i) for (j=1;j<=s2;++j)
    	inc(ans,1LL*f[n][i][j]*fact[i]%mod*quick_pow(j,mod-1-i)%mod*s2%mod*quick_pow(j)%mod);
    	return printf("%d",ans),0;
    }
    
    

    F - Two Permutations

    分类讨论题。比起E来说可真是清真了好多。。。

    首先根据某个经典套路,我们把排列(p,q)都看做置换,然后把在一个圈里的位置搞出来,它们显然是要同时选(p,q)或同时不选的

    考虑讨论(i,p_i,q_i)之间的关系,则有:

    1. (i=p_i=q_i):此时换不换都是一样的
    2. (i ot =p_i=q_i):此时任意改变且只改变(p_i,q_i)中的一个可以使答案(+1)
    3. (i=p_i ot =q_i):此时改变(q_i)会使答案(-1)
    4. (i=q_i ot =p_i):此时改变(p_i)会使答案(-1)
    5. (i ot = q_i ot = p_i):此时同时改变(p_i,q_i)会使得答案(-1)

    然后你看到了那个8s的数据范围,会发现是出题人在提醒你用复杂度大点的算法来做,同时这里还有互相选择的限制,因此很容易想到转化成最小割的问题

    那么最小割不允许出现(+1)的情况啊。没事我们把第(2)条稍加修改,变成此时不改变(p_i,q_i)或同时改变(p_i,q_i)会使答案(-1)

    那么我们令(p)中的点分在(S)表示改变,分在(T)集表示不变;(q)中的点分在(S)表示不变,分在(T)集表示改变

    使用Dinic即可在(O(nsqrt n))的复杂度内解决此题

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #define RI register int
    #define CI const int&
    using namespace std;
    const int N=200005,INF=1e9;
    struct edge
    {
    	int to,nxt,v;
    }e[N<<2]; int n,head[N],cnt=1,p[N],q[N],fp[N],fq[N],cur,s,t;
    inline void addedge(CI x,CI y)
    {
    	e[++cnt]=(edge){y,head[x],1}; head[x]=cnt;
    	e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
    }
    class Network_Flow
    {
    	private:
    		int q[N],dep[N],cur[N];
    		#define to e[i].to
    		inline bool BFS(CI s,CI t)
    		{
    			RI H=0,T=1; memset(dep,-1,t+1<<2);
    			dep[q[1]=s]=0; while (H<T)
    			{
    				int now=q[++H]; for (RI i=head[now];i;i=e[i].nxt)
    				if (e[i].v&&!~dep[to]) dep[to]=dep[now]+1,q[++T]=to;
    			}
    			return ~dep[t];
    		}
    		inline int DFS(CI now,CI tar,int dis)
    		{
    			if (now==tar) return dis; int ret=0;
    			for (RI& i=cur[now];i&&dis;i=e[i].nxt)
    			if (e[i].v&&dep[to]==dep[now]+1)
    			{
    				int dist=DFS(to,tar,min(dis,e[i].v));
    				dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
    			}
    			if (!ret) dep[now]=-1; return ret;
    		}
    		#undef to
    	public:
    		inline int Dinic(CI s,CI t,int ret=0)
    		{
    			while (BFS(s,t)) memcpy(cur,head,t+1<<2),ret+=DFS(s,t,INF); return ret;
    		}
    }NF;
    int main()
    {
    	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&p[i]),++p[i];
    	for (i=1;i<=n;++i) scanf("%d",&q[i]),++q[i];
    	for (i=1;i<=n;++i) for (cur=i;!fp[cur];cur=p[cur]) fp[cur]=i;
    	for (i=1;i<=n;++i) for (cur=i;!fq[cur];cur=q[cur]) fq[cur]=i;
    	for (cur=0,t=(n<<1)+1,i=1;i<=n;++i) if (p[i]==q[i])
    	{
    		if (p[i]!=i) ++cur,addedge(fp[i],fq[i]+n),addedge(fq[i]+n,fp[i]);
    	} else
    	{
    		++cur; if (p[i]==i) addedge(s,fq[i]+n); else
    		if (q[i]==i) addedge(fp[i],t); else addedge(fp[i],fq[i]+n);
    	}
    	return printf("%d",cur-NF.Dinic(s,t)),0;
    }
    
    

    Postscript

    我太菜了对思维题还是一无所知。。。

  • 相关阅读:
    SetConsoleScreenBufferSize 函数--设置控制台屏幕缓冲区大小
    GetConsoleScreenBufferInfo 函数--获取控制台屏幕缓冲区信息
    CONSOLE_SCREEN_BUFFER_INFO 结构体
    GetStdHandle 函数--获取标准设备的句柄
    设计模式之代理模式(Proxy Pattern)_远程代理解析
    设计模式之状态模式(State Pattern)
    设计模式之组合模式(Composite Pattern)
    设计模式之迭代器模式(Iterator Pattern)
    设计模式之模版方法模式(Template Method Pattern)
    设计模式之外观模式(Facade Pattern)
  • 原文地址:https://www.cnblogs.com/cjjsb/p/11619667.html
Copyright © 2020-2023  润新知