周二至周四练题题解

周二至周四练题题解

数学作业

这个题还挺简单的。

一看 \(n\le 10^{18}\),多半是结论题或者矩阵乘法优化DP

这里后者挺好想。设 \(f[i]\) 表示 \(n=i\) 时的答案,\(S(k)\) 表示 \(k\)在十进制下的位数。

显然有: \(f[i]=(f[i-1]\times S(i-1)+i)\bmod m\)

容易看出,\(S\) 的取值是连续的,且最多有 \(18\) 个不同,则可以分段进行矩阵乘法。

\([10^k,10^{k+1}-1]\) 分为一段进行转移即可。

容易构造状态矩阵: \(F[i]=[f[i],i,1]\)

同样就很轻松可以算出转移矩阵 \(A[k]=\begin{bmatrix}10^k & 0 & 0\\ 1 & 1 & 0\\ 0 & 1 & 1\end{bmatrix}\).

\(F[0]=[0,0,1]\)

\(F[n]=f[0]\left(\prod_{i=1}^{S(n)-1} A[i]^{9\times 10^{i}}+A[S(n)]^{n-10^{S(n)}+1}\right)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
int m,l;
ll n;
/*省去矩阵类和快速幂*/
void init(){
	cin>>n>>m;
	if(n==0){
		cout<<"0\n";exit(0);
	} int p=n;
	while(p)
		l++,p/=10;
	l--;
}
void solve(){
	memset(f.a,0,sizeof f.a);
	f.a[0]=0,f.a[1]=1,f.a[2]=1;
	memset(A.a,0,sizeof A);
	A.a[1][0]=A.a[1][1]=A.a[2][1]=A.a[2][2]=1;
	A.a[0][0]=10;
	int pow=1;
	for(int i=1;i<=l;i++){
		pow=pow*10;
		A.a[0][0]=pow%m;
		f=f*power(A,pow-pow/10);
	}
	A.a[0][0]=pow*10%m;
	f=f*power(A,n-pow+1);
	cout<<f.a[0];
}
signed main(){
	init();solve();
	return 0;
}

卡片占卜

容易发现,反转操作是很烦人的。但题目中 I/O 很容易让人联想到 1/0。这启发我们将其化为一个 0/1 序列进行操作,翻转等价于这一段所有数异或1。

但这还不够,我们时间复杂度不允许区间操作,只能搞单点。

之前说到了,等价于这一段所有数异或1,则容易联想到使用异或来搞事情。

\(len=A+B+C+D+E\),则我们将整个数组改为当前数与后面一个数的异或值,容易发现整个序列只剩下5个1(默认原序列中\(len+1\) 是0)。

那么每一次操作 \([l,r]\) 都等价于 \(a[l]=a[l]\oplus 1,a[r+1]=a[r+1]\oplus 1\)

由于 \(a[x]\oplus 1\oplus 1=a[x]\),所以我们可以将每一个操作 \([l,r]\),连边 \((l,r+1)\)

需要注意的是,要保证序列是正面朝上,由于原序列中 \(len+1\) 永远代表0,不会被修改,则异或后最后一个数必须为 1。我们仅需将前面 4 个 1改成0即可。

容易想到,跑最短路之后,就可以求出将两点异或1的最小代价。但这里涉及到了四个点,怎么办?枚举四个点的两两对应的最短路组合即可,只有 3 种,显然,由于最短路的最短性,一定存在某方案不会重复操作。

电压

下面定义奇环/偶环为环上节点个数的奇偶性

由于一个奇环一定不合法(头尾属性一样),若选择了一个电阻后,删掉这条边之后仍然存在奇环,则这个点不能选

问题化为高效判断一个点是否删掉后不存在奇环。

首先是原本的奇环应该消失:即所有的奇环都应该经过这条边

其次是新生的奇环数目为0:偶环被删边之后,与奇环的交会形成奇环。(也即一个奇环被删,一个偶环被删,由于这条边在图上有奇环经过,则删掉后一个奇环和偶环会合并为一个奇环。)

那么我们取一颗树(实际上是森林)来进行操作,这里取的是DFS树,很显然,除非奇环数量唯一,否则只有可能树边产生贡献。

等价于说我们枚举没有在树上的边,判断构成的是奇环还是偶环,奇环就给所有边权加一,否则就是标记不能选。

这一步可以使用树链剖分,但事实上不需要。

可以类比 tarjan LCA算法的思想,使用一次DFS直接处理出奇偶环数量的差分数组。然后暴力枚举点判断即可。

注意这一步我们是将边视为了点,所以树的根节点即使满足条件也不能选。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 505000
int head[N],tot=1,ver[N<<1],nxt[N<<1],dep[N],num,s[N][2],vis[N],cnt,n,m,ans,f[N],sum[N],tag[N];
void add(int u,int v){
    nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs(int u,int fa){
    dep[u]=dep[fa]+1,vis[u]=1;
    for(int i=head[u];i;i=nxt[i]){
        int v=ver[i];
        if(i==f[u]||i==(f[u]^1))continue;
        if(vis[v]){
            if(dep[v]>dep[u])continue;
            int x=(dep[u]-dep[v])&1;
            s[u][x]++,s[v][x]--;
            if(!x)++cnt;
        }
        else {
        	f[v]=i;
            dfs(v,u);
            for(int i=0;i<=1;i++)s[u][i]+=s[v][i];
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++)if(!vis[i])dfs(i,0);
    if(!cnt){
        cout<<m<<"\n";
        
    }
    for(int i=1;i<=n;i++)ans+=(s[i][0]==cnt&&s[i][1]==0&&f[i]!=0);
    ans+=(cnt==1); 
    cout<<ans<<"\n";
}
posted @ 2023-03-05 07:38  spdarkle  阅读(7)  评论(0编辑  收藏  举报