2023牛客暑期多校训练营3

A.World Fragments I

题意:给两个数x和y,可以进行以下操作任意次:

选择x二进制位上的某个数b,进行x+b或者x-b,问x最少需要多少次操作才能变成y

Solution

其实x上有1的话就只能+1或者-1,如果x是0的话就动不了,所以答案要么是|x-y|,要么是-1,

void solve()
{
	string s;cin>>s;
	string t;cin>>t;
	int x=0,y=0;
	for(int i=0;i<s.length();i++)
	{
		x<<=1;
		x+=s[i]-'0';
	}
	
	for(int i=0;i<t.length();i++)
	{
		y<<=1;
		y+=t[i]-'0';
	}
	if(x==0&&x!=y)
	{
		cout<<"-1\n";
		return;
	}
	//cout<<x<<" "<<y<<"\n";
	cout<<abs(y-x)<<"\n";
}

B.Auspiciousness

题意:有2n张卡牌,上面的数值分别为1到2n,可以进行抽卡,一开始抽一张卡,抽完第一张卡后猜下一张是更大还是更小,并且再抽一张卡,如果猜对了可以继续猜卡与抽卡,猜错了则停止猜卡与抽卡。现在如果抽到的卡牌是小于等于n的则猜大,反之猜小,问所有排列情况可能抽多少张卡。

Solution

懵哥的题解,看了半天才明白

令dp[x][y][k][0/1]表示当前剩余x张1到n卡和y张n+1到2n卡,当前抽的卡是第2n-x-y+1张,也就是抽完后剩余x张1到n卡和y张n+1到2n卡

0:抽到的卡小于等于n

1:抽到的卡大于n

k:若上一次抽卡是小于等于n的,上一次抽卡抽到的是第k小的,若上一次抽卡是大于n的,则上一次抽卡抽到的是第k大的

dp[x][y][k][0/1]能到达该种状态的合法方案数

要想继续抽卡,则我们可以发现

\[dp[x][y][k][0]=\sum_{i=1}^{k-1}dp[x+1][y][i][0]+\sum_{i=1}^{y+1}dp[x][y+1][i][1] \]

\[dp[x][y][k][1]=\sum_{i=1}^{k-1}dp[x][y+1][i][1]+\sum_{i=1}^{x+1}dp[x+1][y][i][0] \]

我们用前缀和k的维度处理一下,得到的就是从1-k的总方案数,每个方案数出现一次我们都可以让其对ans的贡献增加

除此以外,每种方案至少能抽一张卡

还有不合法的方案下,即每种方案的贡献再计算一遍,但是对于全部抽完的合法方案来说,它不可能在抽一遍了,所以要减去此方面的贡献。

对于题目来说,这种方案的空间复杂度有点大,会MLE

我们考虑到x只与当前和上次的状态有关,所以可以将其用0和1表示当前和上一次的状态,其实y也一样。

这样就可以减少空间复杂度

大题的思路跟之前是一样的

在每次算完当前y状态之后转变x的状态

懵哥那个最后计算的不太懂,我改成了在中途把全选的算了一遍

int n,p;
int fac[2005];
int inv[2005];
void init()
{
	fac[0]=1;
	for(int i=1;i<=1000;i++)
	{
		fac[i]=(fac[i-1]*i)%p;
	}
	inv[1000]=ksm(fac[1000],p-2,p);
	for(int i=999;i>=0;i--)
	{
		inv[i]=(inv[i+1]*(i+1))%p;
	}
}
int C(int n,int m)
{
	if(n<m||n<0||m<0)return 0;
	else return ((fac[n]*inv[n-m])%p*inv[m])%p;
}

int dp[2][305][305][2];



void solve()
{
	
	cin>>n>>p;
	init();
	for(int i=0;i<=1;i++)
	{
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=n;k++)
			{
				dp[i][j][k][0]=dp[i][j][k][1]=0;
			}
		}
	}
	//for(int i=1;i<=n;i++)cout<<fac[i]<<" \n";
	
	int ans=fac[2*n];
	//cout<<ans<<"\n";
	int op=0;
	for(int i=1;i<=n;i++)
	{
		dp[0][n][i][0]=i%p;
		dp[0][n][i][1]=i%p;
	}
	for(int x=n;x>=0;x--)
	{
		for(int y=n;y>=0;y--)
		{
			//cout<<x<<" "<<y<<"\n";
			if(x+y>=2*n)continue;
			for(int k=1;k<=x;k++)
			{
				if(x!=n)
				dp[op][y][k][0]=(dp[op][y][k][0]+((dp[op^1][y][x+1][0]-dp[op^1][y][k][0])%p+p)%p)%p;
				if(y!=n)
				dp[op][y][k][0]=(dp[op][y][k][0]+dp[op][y+1][y+1][1])%p;
				
			}
			for(int k=1;k<=x;k++)
			{
				dp[op][y][k][0]=(dp[op][y][k][0]+dp[op][y][k-1][0])%p;
			}
			ans=(ans+dp[op][y][x][0]*fac[x+y-1]%p)%p; //把每种方案与剩余的全排列都计算一遍得到贡献
			// cout<<ans<<"\n";
			for(int k=1;k<=y;k++)
			{
				if(y!=n)
				dp[op][y][k][1]=(dp[op][y][k][1]+((dp[op][y+1][y+1][1]-dp[op][y+1][k][1])%p+p)%p)%p;
				if(x!=n)
				dp[op][y][k][1]=(dp[op][y][k][1]+dp[op^1][y][x+1][0])%p;
				
				
			}
			for(int k=1;k<=y;k++)
			{
				dp[op][y][k][1]=(dp[op][y][k][1]+dp[op][y][k-1][1])%p;
			}
			ans=(ans+dp[op][y][y][1]*fac[x+y-1]%p)%p;
			//cout<<ans<<"\n\n";
		}
		op ^= 1;
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=n;j++)
			{
				dp[op][i][j][0]=dp[op][i][j][1]=0;
			}
		}
		if(x==1)
		{
			ans=((ans-dp[op^1][0][1][0])%p+p)%p;
		}
	}
	//cout<<ans<<"\n";
	
	ans=(ans+((fac[2*n]-dp[op^1][1][1][1]%p)%p+p)%p)%p;//下一步是输的话再算一遍
	cout<<ans<<"\n";
}

D.Ama no Jaku

题意:给出一个01矩阵,规定横着的作为一个数组的二进制数,竖着的作为一个数组的二进制数

比如

110

000

001

这样一个矩阵,横着的数有[110,000,001]即[6,0,1],竖着的有[100,100,001]即[4,4,1],如果可以进行任意次操作,每次操作可以将一整行或者一整列01反转,问最少需要多少次才能使得min(横着的数)>=max(竖着的数)

Solution

只有两种情况能成立:全0和全1矩阵

那么只要看它能不能变成全0或者全1即可,然后直接取行变换和列变换的最小值之和即可

void solve()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			char c;cin>>c;
			a[i][j]=c-'0';
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(a[i][1]==1)
		for(int j=1;j<=n;j++)
		{
			b[i][j]=a[i][j]^1;
		}
		else 
		for(int j=1;j<=n;j++)
		{
			b[i][j]=a[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(b[j][i]!=b[1][i])
			{
				cout<<"-1\n";
				return;
			}
		}
	}
	int ans1=0,ans2=0;
	int ans3=0,ans4=0;
	for(int i=1;i<=n;i++)
	{
		if(a[1][i]==1)ans1++;
		else ans2++;
	}
	for(int i=1;i<=n;i++)
	{
		if(a[i][1]==1)ans3++;
		else ans4++;
	}
	cout<<min(ans1,ans2)+min(ans3,ans4)<<"\n";
	
}

E.Koraidon, Miraidon and DFS Shortest Path

题意:给出一个有向图,边长均为1,问能否在随机遍历边的情况下,使得所有情况下得到的最短路长度都相同

Solution

首先bfs遍历一遍图得到所有点的深度

考虑到只有一种情况会使得最短路长度不同

对于边<u,v>当且仅当dis[v]!=dis[u]+1且v不是u的支配点,如果v不是u的支配点,那么肯定有一条路可以不经过v到达u,从而对v的dis值产生影响

套板子(不会了

struct MAP {
    struct Edge {
        int to, next;
    } edge[N << 1];
    int tot, head[N];
    void addEdge(int x, int y) {
        edge[++tot].to = y;
        edge[tot].next = head[x];
        head[x] = tot;
    }
};
MAP G, GF;             //原图、反图
MAP dfsTree, dfsTreeF; // dfs树、dfs树的反图
MAP dominate;          //支配树
 
int n, m;
int fa[N];          // dfs树上的父节点
int dfn[N], id[N], tim; // dfs序、标号、时间戳
 
void dfs(int x) {
    id[++tim] = x;
    dfn[x] = tim;
 
    for (int i = G.head[x]; i; i = G.edge[i].next) {
        int to = G.edge[i].to;
        if (!dfn[to]) {
            dfs(to);
            fa[to] = x;
            dfsTree.addEdge(x, to);
        }
    }
}
 
int sdom[N]; //半支配点
int mn[N]; // mn[i]表示i点的dfs树上的sdom最小的祖先,因此有sdom[mn[i]]=sdom[i]
int anc[N];       // anc[i]代表i的祖先
int find(int x) { //路径压缩的带权并查集
    if (x != anc[x]) {
        int t = anc[x];
        anc[x] = find(anc[x]);
        if (dfn[sdom[mn[x]]] > dfn[sdom[mn[t]]])
            mn[x] = mn[t];
    }
    return anc[x];
}
void LengauerTarjan() { //寻找半支配点
    for (int i = 1; i <= n; i++) {
        anc[i] = i;
        sdom[i] = i;
        mn[i] = i;
    }
    for (int j = n; j >= 2; j--) {
        int x = id[j];
        if (!x)
            continue;
 
        int pos = j;
        for (int i = GF.head[x]; i; i = GF.edge[i].next) {
            int y = GF.edge[i].to;
            if (!dfn[y])
                continue;
            if (dfn[y] < dfn[x])
                pos = min(pos, dfn[y]);
            else {
                find(y); //寻找树上y的一个满足dfn[z]>dfn[x]的祖先z
                pos = min(pos, dfn[sdom[mn[y]]]);
            }
        }
        sdom[x] = id[pos];
        anc[x] = fa[x];
        dfsTree.addEdge(sdom[x], x); //在dfs树上连边
    }
}
 
int deep[N], dp[N][25];
int getLCA(int x, int y) { //获取LCA
    if (deep[x] < deep[y])
        swap(x, y);
    int del = deep[x] - deep[y];
    for (int i = 0; i <= 20; i++)
        if ((1 << i) & del)
            x = dp[x][i];
    if (x == y)
        return x;
    for (int i = 20; i >= 0; i--) {
        if (dp[x][i] != dp[y][i]) {
            x = dp[x][i];
            y = dp[y][i];
        }
    }
    return dp[x][0];
}
void buildDominate(int x) { //建立支配树
    int to = dfsTreeF.edge[dfsTreeF.head[x]].to;
    for (int i = dfsTreeF.head[x]; i; i = dfsTreeF.edge[i].next) {
        int y = dfsTreeF.edge[i].to;
        to = getLCA(to, y);
    }
    deep[x] = deep[to] + 1;
    dp[x][0] = to;
    dominate.addEdge(to, x);
 
    for (int i = 1; i <= 20; i++)
        dp[x][i] = dp[dp[x][i - 1]][i - 1];
}
int in[N]; // dfs树的入度
void topSort() {
    for (int i = 1; i <= n; i++) {
        for (int j = dfsTree.head[i]; j; j = dfsTree.edge[j].next) {
            int to = dfsTree.edge[j].to;
            in[to]++;
            dfsTreeF.addEdge(to, i); //对DFS树的反图建边
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {
            dfsTree.addEdge(0, i);  // dfs树建边
            dfsTreeF.addEdge(i, 0); // dfs树的反图建边
        }
    }
 
    queue<int> Q;
    Q.push(0);
    while (Q.size()) {
        int x = Q.front();
        Q.pop();
        for (int i = dfsTree.head[x]; i; i = dfsTree.edge[i].next) {
            int y = dfsTree.edge[i].to;
            if ((--in[y]) <= 0) {
                Q.push(y);
                buildDominate(y); //建立支配树
            }
        }
    }
}

void reset()
{
	for(int i=0;i<=n;i++)
	{
		in[i]=deep[i]=dfn[i]=id[i]=fa[i]=0;
		for(int j=0;j<=20;j++)dp[i][j]=0;
		G.head[i]=GF.head[i]=dfsTree.head[i]=dfsTreeF.head[i]=dominate.head[i]=0;
	}
	tim=0;
	//memset(dp,0,sizeof(dp));
	G.tot=0;
	GF.tot=0;
	dfsTree.tot=0;
	dfsTreeF.tot=0; // dfs树、dfs树的反图
	dominate.tot=0;    
}
 
/*int idom[N];
void dfsDominate(int x) { //在支配树上搜索idom
    idom[x] = 1;
    for (int i = dominate.head[x]; i; i = dominate.edge[i].next) {
        int y = dominate.edge[i].to;
        dfsDominate(y);
        idom[x] += idom[y];
    }
}

*/


void solve()
{
	cin>>n>>m;
	reset();
	
	for(int i=1;i<=m;i++)
	{
		int u,v;cin>>u>>v;
		if(u==v)continue;
		G.addEdge(u,v);
    	GF.addEdge(v,u);
	}
	dfs(1);           // dfs,求出dfs序
    LengauerTarjan(); //计算半支配点sdom
    topSort();      //根据dfs树建立dfs树的反图,并对进行拓扑排序从而建立支配树
   // dfsDominate(0); //在支配树上寻找答案
	vector<int>dis(n+1,-1);
	queue<int>q;
	q.push(1);
	dis[1]=0;
	while(q.size())
	{
		int t=q.front();
		q.pop();
		for (int i = G.head[t]; i; i = G.edge[i].next) {
        	int y = G.edge[i].to;
        	//cout<<t<<" "<<y<<"\n";
        	if(dis[y]==-1)
        	{
        		dis[y]=dis[t]+1;
        		q.push(y);
        	}else if(dis[y]==dis[t])
        	{
        		cout<<"No\n";
        		return;
        	}else if(dis[y]!=dis[t]+1)
        	{
        		int x=getLCA(y,t);
        		
        		if(x!=y)
        		{
        			cout<<"No\n";
        			return;
        		}
        	}
   		 }
	}
	cout<<"Yes\n";
	
}

H.Until the Blue Moon Rises

题意:给出一个数组,每次可以选择两个数的i和j,令a[i]+1,a[j]-1,问能否把所有数都变成质数

Solution

利用哥德巴赫猜想:一个大于2的偶数可以由两个质数组成

考虑n的大小关系

n=1时,a[1]必须为质数

n=2时,sum如果为偶数则成立,如果不为偶数,那么由于奇数只能由偶数+奇数得到,所以sum-2必须是质数

n>2时,如果sum为偶数,那么sum>=2*n成立,如果sum为奇数

那么sum-3>=(2*n-2)成立,综合来说,sum>=2*n

void solve()
{
	int n;cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		sum+=a[i];
	}
	
	if(n==1)
	{
		if(a[1]<2)
		{
			cout<<"No\n";
			return;
		}
		for(int i=2;i*i<=a[1];i++)
		{
			if(a[1]%i==0)
			{
				cout<<"No\n";
				return;
			}
		}
		cout<<"Yes\n";
	}else if(n==2)
	{
		if(sum&1)
		{
			int x=sum-2;
		}
		if(x<2)
		{
			cout<<"No\n";
			return;
		}
		for(int i=2;i*i<=x;i++)
		{
			if(x%i==0)
			{
				cout<<"No\n";
				return;
			}
		}
		cout<<"Yes\n";
		}else
		{
			cout<<(sum!=2?"Yes\n":"No\n");
		}
	}else
	{
		cout<<(sum>=2*n?"Yes\n":"No\n");
	}
}

J.Fine Logic

题意:给定n个点和m对偏序关系<u,v> ,构造最少的排列数目k,使得在这k个排列中至少有一个排列满足u<v。

Solution

要包含所有偏序关系,最多两个排列就行了

[1,2,...,n]

[n,n-1,...,1]

如果需要两个排列的话,就证明其中必定有冲突的地方,把它看成一个有向图,进行拓扑排序,如果拓扑完没有环,则可以按拓扑的顺序来排列,反之则按上面的排序来

void solve()
{
	int n,m;cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;cin>>x>>y;
		e[x].push_back(y);
		t[x]++,p[y]++;
	}
	queue<int>q;
	vector<int>ans;
	set<int>st;
	for(int i=1;i<=n;i++)
	{
		if(st.count(i))continue;
		if(p[i]!=0)continue;
		q.push(i);
		
		while(q.size())
		{
			int x=q.front();
			q.pop();
			ans.push_back(x);
			st.insert(x);
			for(auto it:e[x])
			{
				p[it]--;
				if(p[it]==0)q.push(it);
			}
			
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		if(p[i]!=0)
		{
			cout<<"2\n";
			for(int i=1;i<=n;i++)cout<<i<<" \n"[i==n];
			for(int i=1;i<=n;i++)cout<<n-i+1<<" \n"[i==n];
			return;
		}
	}
	cout<<"1\n";
	for(auto it:ans)cout<<it<<" ";
	cout<<"\n";
}
posted @ 2023-07-27 15:10  HikariFears  阅读(49)  评论(0)    收藏  举报