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\) ,没有倍数的充要条件是
将左式移到等号右边,那么式子的值只可能是 \(0/1\) 。因此只需要计算
直接用 AtCoder Library 的 floor_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\) 的概率。
然后计算 \(p[j][v][1]\) 的递推式:
令 \(a[i]=1-\dfrac{m}{2m+1}\times P[i],b[v]=\dfrac{m-v}{2m+1}\) ,有
于是有
那么
最后那个东西可以直接数列推导。
最后答案为
//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;
}

浙公网安备 33010602011771号