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]能到达该种状态的合法方案数
要想继续抽卡,则我们可以发现
我们用前缀和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";
}

浙公网安备 33010602011771号