AtCoder Regular Contest 111

Contest Link Official Editorial

A - Simple Math 2

给定 \(N,M(N\leq 1e18,M\leq 1e4)\) ,求 \(\displaystyle \Big\lfloor \dfrac{10^N}{M}\Big\rfloor\)

Solution

我的解法:这道题长得很像快速幂(虽然不能用),容易想到去考虑倍增,计算 \(10^{2^i}\bmod m\) 的整数部分和余数部分,最后再把 \(N\) 二进制拆分,统计答案即可。问题转化为 \((am+b)\times (cm+d)\bmod m\) 的整数部分和余数部分,将式子拆开计算即可。

注:此处所有“整数部分”均指整除部分对 \(m\) 取模之后的结果。

//Author: RingweEH
ll n,m,x[110],y[110];
 
int main()
{
    n=read(); m=read();
    x[0]=10/m; y[0]=10%m;
    for ( ll i=1; (1ll<<i)<=n; i++ )
    {
        x[i]=2ll*x[i-1]*y[i-1]%m; x[i]+=y[i-1]*y[i-1]/m; x[i]%=m;
        y[i]=y[i-1]*y[i-1]%m;
    }
    ll sumx=0,sumy=0;
    for ( ll i=0; (1ll<<i)<=n; i++ )
        if ( n&(1ll<<i) )
        {
            if ( sumx==0 && sumy==0 ) 
            {
                sumx=x[i]; sumy=y[i]; continue;
            }
            sumx=sumx*y[i]%m+sumy*x[i]%m; sumx%=m;
            sumx=sumx+sumy*y[i]/m; sumx%=m;
            sumy=sumy*y[i]%m;
        }
    printf( "%lld\n",sumx%m );

    return 0;
}

正解:把 \((am+b)\times (cm+d)\bmod m\) 拆开的过程中,注意到 \(am^2\) 这一项是没用的,因为整除 \(m\) 之后 \(\bmod m=0\) . 于是就可以直接跑快速幂,对 \(m^2\) 取模。

n, m = map(int, input().split())
print(pow(10, n, m * m) // m % m)

B - Reversible Cards

给定 \(n\) 张牌,每张牌两面都有一个数,任意翻转,问最优情况下朝上的一面中有多少不同的数。

Solution

赛后听说是2020牛客原题……牛客看上去还不错的样子??不过一直很讨厌强制手机号注册的东西。还不是没有手机

显然可以以数为顶点,把每张牌的两面上的数字连边。问题转化为:每条边的两个端点选择一个,问最多能选择多少个顶点。

于是就会发现,对于每个连通块(显然可以分开处理),贡献是 \(\min(边数,点数)\) 。并查集找连通块就好了。

//Author: RingweEH
const int N=4e5+10;
int n,x[N],y[N],fa[N],e[N],sum[N],sumNode[N];
bool vis[N];

int find( int x )
{
    return (x==fa[x]) ? x : fa[x]=find(fa[x]);
}

int main()
{
    n=read();
    for ( int i=1; i<=N-10; i++ )
        fa[i]=i;
    for ( int i=1; i<=n; i++ )
    {
        x[i]=read(); y[i]=read();
        int fx=find(x[i]),fy=find(y[i]);
        if ( fx!=fy ) fa[fx]=fy;
        vis[x[i]]=vis[y[i]]=1;
    }

    for ( int i=1; i<=N-10; i++ )
        e[i]=find(i),sumNode[e[i]]++;
    for ( int i=1; i<=n; i++ )
        sum[e[x[i]]]++;

    int ans=0;
    for ( int i=1; i<=N-10; i++ )
        if ( vis[i] && e[i]==i ) ans+=min( sum[i],sumNode[i] );
    printf( "%d\n",ans );
}

C - Too Heavy

\(n\) 个人拿着 \(n\) 个包,每个人和包各有一个重量,包有标号,每次仅允许交换两个人的包,目标是让每个人的编号对应包的标号,问最少操作次数。限制:包的重量不小于人就不能再换。

Solution

无解:一开始包就不比人轻,且没有对应正确的标号。

\(i\)\(p_i\) 连边,由于 \(p\)\(1\sim n\) 的一个排列,所以显然是一个循环的集合。令 \(C\) 表示循环的个数。

考虑交换次数的下限,显然是 \(n-C\) . 现在来单独考虑每个循环。注意到,如果要交换 \(i,j\)\(a_i\ge a_j\)\(i\) 个人不会累死。

那么,如果在每个循环里面选择最轻的人 \(i\) ,交换之后问题规模会减少 \(1\) ,这样总操作次数就是 \(n-C\) ,显然操作次数不会更少。

具体实现并不需要真的找出每个循环,只需要按重量升序操作即可。

//Author:RingweEH
#define PII pair<int,int>
#define mp make_pair
const int N=2e5+10;
int a[N],b[N],p[N],n,to[N],id[N];	//编号为 i 的行李现在在 to[i] 手上
vector<PII> opt;

bool cmp( int x,int y )
{
	return a[x]<a[y];
}

int main()
{
	n=read();
	for ( int i=1; i<=n; i++ )
		a[i]=read(),id[i]=i;
	for ( int i=1; i<=n; i++ )
		b[i]=read();
	for ( int i=1; i<=n; i++ )
		p[i]=read(),to[p[i]]=i;

	for ( int i=1; i<=n; i++ )
		if ( b[p[i]]>=a[i] && p[i]!=i ) { printf( "-1" ); return 0;}
	sort( id+1,id+1+n,cmp ); int ans=0;
	for ( int i=1; i<=n; i++ )
	{
		int nw=id[i];
		if ( p[nw]!=nw )
		{
			ans++; opt.push_back( mp(nw,to[nw]) );
			int tmp=to[nw]; swap( p[nw],p[tmp] );
			to[p[tmp]]=tmp; to[p[nw]]=nw;
		}
	}

	printf( "%d\n",ans );
	for ( int i=0; i<opt.size(); i++ )
		printf( "%d %d\n",opt[i].first,opt[i].second );

	return 0;
}

D - Orientation

给定一个 \(n\)\(m\) 边的无向图,每个点有一个标号 \(c[i]\) ,给每条边确定一个方向,使得 \(i\) 能到达的点的个数为 \(c[i]\) .

Solution

注意到对于每一条边,如果是 \((u,v)\)\(c[u]>c[v]\) ,那么一定是 \(v\to u\) 。将这些判断完之后,只需要一遍 DFS 判断出剩下的边的方向即可。

//Author: RingweEH
const int N=110;
int n,m,mp[N][N],c[N],e[N*N][5];
bool vis[N];
 
void DFS( int u )
{
	vis[u]=1;
	for ( int v=1; v<=n; v++ )
		if ( c[u]==c[v] && mp[u][v] ) 
		{
			if ( mp[u][v]>0 ) e[mp[u][v]][2]=0;
			else e[-mp[u][v]][2]=1;
			if ( !vis[v] ) DFS( v );
		}
}

int main()
{
	n=read(); m=read();
	for ( int i=1; i<=m; i++ )
	{
		int u=read(),v=read();
		e[i][0]=u; e[i][1]=v; mp[u][v]=i; mp[v][u]=-i;
	}
	for ( int i=1; i<=n; i++ )
		c[i]=read();

	for ( int i=1; i<=m; i++ )
	{
		int u=e[i][0],v=e[i][1];
		if ( c[u]<c[v] ) e[i][2]=1;
		else if ( c[u]>c[v] ) e[i][2]=0;
	}
	for ( int i=1; i<=n; i++ )
		if ( !vis[i] ) DFS( i );

	for ( int i=1; i<=m; i++ )
		if ( !e[i][2] ) printf( "->\n" );
		else printf( "<-\n" );

	return 0;
}

E - Simple Math 3

给定 \(A,B,C,D\) ,找出满足以下条件的正整数 \(i\) 的个数:

  • \(A+B\times i\sim A+C\times i\) 均不能整除 \(D\)

Solution

如果 \(C\times i-B\times i+1\ge D\) ,那么一定有。

\(\displaystyle n=\Big\lfloor \frac{D-2}{C-B}\Big\rfloor\) ,我们只需要考虑 \(0<i\leq n\) ,此时 \(C\times i-B\times i+1<D\) ,没有倍数的充要条件是

\[\Big\lfloor \dfrac{A+B\times i-1}{D}\Big\rfloor =\Big\lfloor \dfrac{A+C\times i}{D}\Big\rfloor \]

将左式移到等号右边,那么式子的值只可能是 \(0/1\) 。因此只需要计算

\[n-\sum_{i=1}^n\Big(\Big\lfloor \dfrac{A+C\times i}{D}\Big\rfloor -\Big\lfloor \frac{A+B\times i-1}{D}\Big\rfloor \Big) \]

直接用 AtCoder Libraryfloor_sum 了。等学了类欧再来补。

//Author:RingweEH
int T,A,B,C,D,n;

int main()
{
	T=read();
	while ( T-- )
	{
		A=read(); B=read(); C=read(); D=read();
		n=(D-2)/(C-B);
		printf( "%d\n",n-floor_sum(n+1,D,C,A)+floor_sum(n+1,D,B,A-1) );
	}

	return 0;
}

F - Do you like query problems?

  • \(opt=1\) :区间取 \(\min\)
  • \(opt=2\) :区间取 \(\max\)
  • \(opt=3\) :区间求和

给定 \(n,m,q\) ,有一个初始为 \(0\) 长度为 \(n\)\(a\) 序列,对所有的 \(\displaystyle (\frac{n(n+1)}{2}(2m+1))^q\) 种操作,求出最终 \(ans\) 的总和,对 \(998244353\) 取模。

Solution

可以将求和转化为求期望。

\(P[i]=\dfrac{2i(n-i+1)}{n(n+1)}\) ,表示一个位置 \(i\) 被操作的概率。

\(p[j][v][0]\) 为第 \(j\) 次操作之后 \(x=v\) 的概率,\(p[j][v][1]\) 为第 \(j\) 次操作之后 \(x\ge v\) 的概率。

\[\begin{aligned} sum[i]&=\sum_{j=1}^{q-1}\sum_{v=0}^{m-1}p[j][v][0]\times v\times P[i]\times \frac{1}{2m+1}\\\\ &=\sum_{j=1}^{q-1}\sum_{v=1}^{m-1}p[j][v][1]\times P[i]\times \frac{1}{2m+1} \end{aligned} \]

然后计算 \(p[j][v][1]\) 的递推式:

\[\begin{aligned} p[j+1][v][1]&=p[j][v][1]\times (1-P[i])+P[i]\times p[j][v][1]\times \frac{2m+1-v}{2m+1}+P[i]\times (1-p[j][v][1])\times \frac{m-v}{2m+1}\\\\ &=p[j][v][1]\times (1-\frac{m}{2m+1}\times P[i])+P[i]\times \frac{m-v}{2m+1} \end{aligned} \]

\(a[i]=1-\dfrac{m}{2m+1}\times P[i],b[v]=\dfrac{m-v}{2m+1}\) ,有

\[p[0][v][1]=0(v\ge 1),p[j+1][v][1]=p[j][v][1]\times a[i]+P[i]\times b[v] \]

于是有

\[p[j][v][1]=P[i]\times b[v]\times \sum_{k=0}^{j-1}a_i^j(j\ge 1) \]

那么

\[\begin{aligned} E &=\sum_{i=1}^n sum[i]=\sum_{i=1}^n\sum_{j=1}^{q-1}\sum_{v=1}^{m-1}P[i]\times \frac{1}{2m+1}\times P[i]\times b[v]\times \sum_{k=0}^{j-1}a_i^j(j\ge 1)\\\\ &=\frac{1}{2m+1}\times (\sum_{v=1}^{m-1}b[v])\times \sum_{i=1}^nP[i]^2\sum_{j=1}^{q-1}\sum_{k=0}^{j-1}a_{i}^j \end{aligned} \]

最后那个东西可以直接数列推导。

最后答案为

\[E\times \left(\frac{n(n+1)}{2} \times (2m+1)\right)^q \]

//Author:RingweEH
const int N=4e5+100,Mod=998244353;
int n,m,q;
ll inv[N];

ll power( ll a,ll b )
{
    ll res=1;
    for ( ; b; b>>=1,a=a*a%Mod )
        if ( b&1 ) res=res*a%Mod;
    return res;
}

int main()
{
    inv[1]=1;
    for ( int i=2; i<=N-10; i++ )
        inv[i]=(Mod-Mod/i)*inv[Mod%i]%Mod;

    n=read(); m=read(); q=read();
    ll mul=power(1ll*n*(n+1)/2%Mod*(2*m%Mod+1)%Mod,q)%Mod;
    mul=mul*inv[2*m+1]%Mod;
    ll sumb=0;
    for ( int i=1; i<m; i++ )
        sumb=(sumb+(m-i)%Mod*inv[2*m+1])%Mod;
    mul=mul*sumb%Mod;
    ll E=0;
    for ( int i=1; i<=n; i++ )
    {
        ll P=1ll*i*(n-i+1)%Mod*inv[n]%Mod*inv[n+1]%Mod*2ll%Mod;
        ll a=1-m*inv[2*m+1]%Mod*P%Mod; a=(a%Mod+Mod)%Mod;
        ll tmp=power( (1+Mod-a)%Mod,Mod-2 );
        ll sum=q-1-a*(1-power(a,q-1))%Mod*tmp%Mod; sum=(sum+Mod)%Mod;
        sum=sum*tmp%Mod; E=(E+sum*P%Mod*P%Mod)%Mod; 
    }

    mul=mul*E%Mod;
    printf( "%lld\n",mul );

    return 0;
}
posted @ 2021-01-11 22:08  MontesquieuE  阅读(101)  评论(0)    收藏  举报