https://codeforces.com/contest/1243/problem/E
题意:
有k个盒子编号1 ~ n,第 i 个盒子里有ni个数。这些数可以为负,但所有数都互不相同。
Ujan从每个盒子里拿出一个数,总共拿出k个。然后他再把这些数放回盒子里,每个盒子里放一个,可能会放回原来的盒子。
如果每个盒子里的数都相同,Ujan就会高兴。
如果Ujan无法高兴,则输出No。否则,第一行输出Yes,接下来k行,第 i 行输出 ci 和 pi ,表示Ujan从第 i 个盒子中取出数字 ci 并把它放入了第 pi 个盒子。
题解:
注意,这些数各不相同
对于每个数,如果它就是该盒子里被拿掉的数,那么该盒子里放进的数是可以确定的
这样就可以构成若干个环
如果答案是Yes,那么这些盒子可以形成若干个环,每个盒子存在且仅存在于一个环。
由于盒子只有15个,可以想到二进制遍历
时间复杂度比较玄学
#include <bits/stdc++.h>
#define ll long long
#define N 16
using namespace std;
int n[N + 5], a[N + 5][5010], vis[1 << N], fr[1 << N], pre[1 << N];
int ans1[N + 5], ans2[N + 5];
ll cnt[20], sum;
map<ll, int> p;
void nxt(ll &x){
ll fa = p[x];
x = sum - (cnt[fa] - x);
}
int main(){
int k; scanf("%d", &k);
for(int i = 1; i <= k; i++){
scanf("%d", &n[i]);
for(int j = 1; j <= n[i]; j++){
scanf("%d", &a[i][j]);
p[a[i][j]] = i;
cnt[i] += 1ll * a[i][j];
}
sum += cnt[i];
}
if(sum % k){ printf("No\n"); return 0;}
sum /= 1ll * k;
for(int i = 1; i <= k; i++){
for(int j = 1; j <= n[i]; j++){
ll now = a[i][j];
ll s = 1 << (p[now] - 1);
bool flag = true;
while(1){
nxt(now);
if(now == a[i][j]) break;
if(!p.count(now)) {flag = false; break;}
if(s & (1 << (p[now] - 1))){ s = -1; break;}
s |= 1 << (p[now] - 1);
}
if(s == -1 || !flag) continue;
fr[s] = a[i][j];
vis[s] = 1;
}
}
for(int i = 0; i < 1 << k; i++) pre[i] = -1;
pre[0] = 0;
for(int i = 0; i < 1 << k; i++){
if(pre[i] == -1) continue;
for (int s = i; s < 1 << k; s = (s + 1) | i)
if (vis[s ^ i]) pre[s] = i;
}
ll full = (1 << k) - 1;
if(pre[full] == -1){ printf("No\n"); return 0;}
printf("Yes\n");
while(full){
ll z = full ^ pre[full];
ll now = fr[z];
while(1){
ll b = p[now];
ans1[p[now]] = now;
nxt(now);
ans2[p[now]] = b;
if(now == fr[z]) break;
}
full = pre[full];
}
for(int i = 1; i <= k; i++) printf("%d %d\n", ans1[i], ans2[i]);
}