牛客 周赛105 20250829
牛客 周赛105 20250829
https://ac.nowcoder.com/acm/contest/115861
A:
题目大意:
void solve(){
int k;
cin>>k;
cout<<1<<' '<<(1^k)<<endl;
}
签到
B:
题目大意:
void solve(){
int n;
cin>>n;
vector<int> a(n+1);
for (int i=1;i<=n;i++) cin>>a[i];
int ans=0;
for (int i=1;i<n;i++)
ans=__gcd(ans,a[i]^a[i+1]);
cout<<ans;
}
模拟一遍即可
C:
题目大意:
void solve(){
int n,k;
cin>>n>>k;
if (k&1){cout<<-1;return;}
k>>=1;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (i==j&&k){
cout<<1;
k--;
}else cout<<0;
}
cout<<endl;
}
}
题目给定的 \(k\le 2n\) ,刚好符合每行每列的异或和全为 \(n\) 的情况
如果在矩阵 \((i,j)\) 的位置上放置一个 \(1\) ,那么第 \(i\) 行,第 \(j\) 列的异或和都会同时改变,换句话说某个位置上的 \(1\) 对答案的贡献要么为 \(0\) 要么为 \(\pm 2\)
所以当 \(k\) 为奇数时一定无解,有解时只在对角线上依次放 \(1\) 就能凑出答案
D:
题目大意:
void solve(){
int n,k;
cin>>n>>k;
int cnt[3]={0};
for (int i=1;i<=n;i++) {
int x;
cin>>x;
cnt[x]++;
}
int L;
if (cnt[2]>cnt[1]) L=cnt[1]*2+2*(cnt[2]-cnt[1]-1);
else L=n-1;
int R=cnt[1]+2*(cnt[2]-1);
if (k>R||k<L){cout<<-1;return;};
vector<int> a(n+1);
int idx=0;
for (int i=1;i<=cnt[1];i++) a[++idx]=1;
for (int i=1;i<=cnt[2];i++) a[++idx]=2;
int i=1,j=cnt[1]+1;
while (R>k){
if (i<1||j>n) {cout<<-1;return;};
swap(a[i],a[j]);
i+=2,j+=1;
R--;
}
for (int i=1;i<=n;i++) cout<<a[i]<<' ';
}
考虑排序可以形成的最大权值和最小权值:
-
最大权值,形如 \(111\cdots1222\cdots2\) ,前一段全 \(1\) 的权值为 \(cnt_1\) 后一段的权值为 \((cnt_2-1)\times 2\)
-
最小权值,形如 \(2121\cdots\) ,交错排列并且考虑 \(1,2\) 的数量
如果 \(k\) 在权值的范围内,那么一定可以通过重新排列达到,(存在改变权值 \(1\) 的操作)
\(111\cdots1222\cdots2\) ,交换其中某个 \(1,2\) 的位置变成 \(211\cdots 1212\cdots2\) 对答案的贡献为 \(-1\)
可以证明,通过不断的交换 \(1,2\) 的操作可以把序列从最大权值的排列变为最小权值的排列
int i=1,j=cnt[1]+1;
while (R>k){
if (i<1||j>n) {cout<<-1;return;};
swap(a[i],a[j]);
i+=2,j+=1;
R--;
}
E:
题目大意:
const int mod=998244353;
vector<int> e[200010];
LL a[200010];
int d[200010][35];
void solve(){
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for (int i=1;i<=n;i++)
for (int j=0;j<31;j++)
d[i][j]=(a[i]>>j)&1;
LL ans=0;
for (int i=1;i<=n;i++){
for (int j=0;j<31;j++){
int cnt1=0,cnt0=0;
for (auto v:e[i]){
if ((a[i]>>j)&1){
if (d[v][j]==0) ans+=cnt0*(1<<j);
else ans+=cnt1*(1<<j);
}else{
if (d[v][j]==0) ans+=cnt1*(1<<j);
else ans+=cnt0*(1<<j);
}
ans%=mod;
if (d[v][j]==1) cnt1++;
else cnt0++;
}
}
}
cout<<ans;
}
枚举每个顶点连接的两个邻居,暴力的最坏时间复杂度为 \(O(n^3)\) 级别
可以考虑通过二进制拆分,每个二进制位在计算异或时是相互独立的,所以枚举顶点后
再以 \(\log\) 的时间复杂度枚举所有的二进制位,每次遍历一遍所有的邻居记录下前缀 \(1,0\) 的数量
-
如果顶点当前的二进制位是 \(1\) ,那么异或产生贡献当且仅当两个邻居的二进制位都相同
if ((a[i]>>j)&1){ if (d[v][j]==0) ans+=cnt0*(1<<j);//当前枚举的邻居为0时,产生的贡献为前缀0的数量 else ans+=cnt1*(1<<j); }
-
如果顶点当前的二进制位是 \(0\) ,那么异或产生贡献当且仅当两个邻居的二进制位都不同
else{ if (d[v][j]==0) ans+=cnt1*(1<<j); else ans+=cnt0*(1<<j); }
总时间复杂度为 \(O((n+m)\log N)\)
F:
题目大意:
void solve(){
int n,m,t;
cin>>n>>m>>t;
struct node{int mg,ms,mk;};
vector<vector<vector<int>>> dp(n+2,vector<vector<int>>(m+2,vector<int>(n*m+2)));
vector<vector<vector<node>>> pre(n+2,vector<vector<node>>(m+2,vector<node>(n*m+2)));
vector<int> ans;
bool f=0;
auto dfs=[&](auto &&self,int x,int pg,int sum){
if (f) return;
if (dp[x][pg][sum]) return;
dp[x][pg][sum]=1;
if (x>n){
if (sum!=t) return;
f=1;
int G=pg,V=sum;
for (int i=n+1;i>1;i--){
auto [mg,ms,mk]=pre[i][G][V];
ans.push_back(mk);
G=mg,V=ms;
}
reverse(ans.begin(),ans.end());
return;
}
for (int i=1;i<=m;i++){
int g=__gcd(i,pg);
if (!dp[x+1][g][sum+g]){
pre[x+1][g][sum+g]={pg,sum,i};
self(self,x+1,g,sum+g);
}
}
};
dfs(dfs,1,0,0);
if (!f){cout<<-1<<endl;return;}
for (auto i:ans) cout<<i<<' ';
cout<<endl;
}
\(n,m=50\) 可以考虑 \(O(n^4)\) 级别的算法,想到用记忆化搜索和前导数组记录方案
\(dp_{i,j,k}\) 表示填了 \(i\) 位数,前缀 GCD 为 \(j\) ,总权值为 \(k\) 的状态是否可达
\(pre_{i,j,k}=\{mg,ms,mk\}\) 分别表示 \(dp_{i,j,k}\) 从前缀 GCD 为 \(mg\) ,总权值为 \(ms\) 且最后一位填 \(mk\) 的状态转移来的
到了递归出口时,如果总权值满足要求,那么通过 \(pre\) 数组回溯构造答案
int G=pg,V=sum;
for (int i=n+1;i>1;i--){
auto [mg,ms,mk]=pre[i][G][V];
ans.push_back(mk);
G=mg,V=ms;
}