2021牛客多校第三场
C - Minimum grid(最大匹配)
想要代价最小,填的数当然是越少越好。对于一个位置,如果它所在的行和列的最大值为x,那么这个位置填x是最优的。如果填的数比x大,矛盾;如果比x小,相当于没填,白白浪费一个位置。
将数组b、c的数称为限定数,那么对于每个x,它有n'个行限定数,m'个列限定数。那么x要满足n'+m'个行和列。设置n'个结点和m‘个结点,如果存在合法位置(i,j)行列最大值均为x,就连一条边(i,j),求最大匹配t。由于必定存在合法方案,剩余需要取的行列就剩下n'+m'-2*t,x所需填的个数为n'+m'-2*t+t=n'+m'-t。x的贡献为x(n'+m'-t)。
代码用的是逆十字的写法,大佬的代码写的就是好。
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e4 + 10;
const int M = 1e6 + 10;
const double eps = 1e5;
typedef long long ll;
struct edge {
int ne, np, f;
};
edge ed[M];
int head[N];
int cur[N];
int si = 2;
int dis[N];
int arr[N];
void init(int n) {
si = 2;
for(int i = 0; i <= n; i++) {
head[i] = 0;
cur[i] = 0;
}
}
void add(int u, int v, int f) {
ed[si] = edge{head[u], v, f};
head[u] = si;
cur[u] = head[u];
si++;
ed[si] = edge{head[v], u, 0};
head[v] = si;
cur[v] = head[v];
si++;
}
bool bfs(int s, int t) {
memset(dis, 0, sizeof dis);
for(int i = 1; i <= t; i++) cur[i] = head[i]; // 当前弧优化,注意这里初始化是所有的点初始化
queue<int> q;
q.push(s);
dis[s] = 1;
while(!q.empty()) {
int cur = q.front();
q.pop();
for(int i = head[cur]; i; i = ed[i].ne) {
int nt = ed[i].np;
if(dis[nt] || (!ed[i].f)) continue;
dis[nt] = dis[cur] + 1;
q.push(nt);
}
}
return dis[t];
}
int dfs(int p, int t, int flo) {
if(p == t) return flo;
int delta = flo;
for(int &i = cur[p]; i; i = ed[i].ne) {
int nt = ed[i].np;
if(dis[nt] == dis[p] + 1 && ed[i].f) {
int d = dfs(nt, t, min(delta, ed[i].f));
delta -= d;
ed[i].f -= d; ed[i^1].f += d;
if(delta == 0) break;
}
}
return flo - delta;
}
ll dini(int s, int t) { // 网络流求最大匹配
ll ans = 0;
while(bfs(s, t)) {
ans += dfs(s, t, INF);
}
return ans;
}
typedef pair<int, int> PII;
int brr[N], crr[N];
int sr[M], sc[M];
vector<PII> pos[M];
unordered_map<int, int> mar, mac;
int main() {
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i++) cin >> brr[i], sr[brr[i]]++;
for(int i = 1; i <= n; i++) cin >> crr[i], sc[crr[i]]++;
for(int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
if(brr[x] == crr[y]) pos[brr[x]].push_back({x, y});
}
ll ans = 0;
for(int i = 1000000; i; i--) {
if(!(sr[i] + sc[i])) continue;
init(sr[i] + sc[i] + 2);
mar.clear(), mac.clear();
int s = 1, t = sr[i] + sc[i] + 2;
for(int j = 2; j <= sr[i] + 1; j++) add(s, j, 1);
for(int j = sr[i] + 2; j < t; j++) add(j, t, 1);
int p1 = 2, p2 = sr[i] + 2;
for(auto p : pos[i]) {
if(!mar[p.first]) mar[p.first] = p1++;
if(!mac[p.second]) mac[p.second] = p2++;
add(mar[p.first], mac[p.second], 1);
}
int res = dini(s, t);
ans += 1ll * (sr[i] + sc[i] - res) * i;
}
cout << ans << endl;
}
E - Math(数学,打表,OEIS)
\((xy+1)|(x^2+y^2) \to k(xy+1)=x^2+y^2\)
将y看作未知量,由韦达定理
\(y^2 -kxy + (x^2-k) = 0 \to\)
\(yy'=x^2-k\)
\(y+y'=kx\)
不妨设\(x\le y\),有\(y'\le x\le y\),\((x, y')\)也是一组解
利用\(y'\)和\(x\)可以求出\(y\),然后由于\(x\)和\(y\)地位相同可以互换,于是又可以用\(x\)和\(y\)求出\(x'\)......
可以一直递增下去,存在递推关系(通过互换\(x\), \(y\)和\(y+y'=kx\))
\((y', x)\to(x, kx-y')\to...\)
初始时\(y'=0\),有
\(y=kx\)
\(k=x^2\)
即初始解为\((0, x)\),\(k=x^2\)。通过枚举不同的\(x\)取值,可得所有解。由于这个递推式递增得很快,直接暴力求即可。将结果储存,二分查找答案。时间复杂度\(O(n^{\frac{1}{3}})\)
当然也可以打表找规律,我太菜,看不出规律。于是我直接去OEIS让它帮我找了(滑稽)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const ll M = 1e18;
vector<ll> ans;
ll dp[N];
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
int main() {
IOS;
for(int k = 2; 1ll * k * k * k <= M; k++) {
dp[0] = 0;
dp[1] = k;
for(int i = 2; ; i++) {
__int128 tmp = 1ll * k * k;
tmp = tmp * dp[i - 1] - dp[i - 2];
if(tmp > M) break;
dp[i] = tmp;
ans.push_back(dp[i]);
}
}
sort(ans.begin(), ans.end());
// cout << ans.size() << endl;
int t;
cin >> t;
while(t--) {
ll n;
cin >> n;
cout << upper_bound(ans.begin(), ans.end(), n) - ans.begin() + 1 << endl;
}
}
I - Kuriyama Mirai and Exclusive Or(异或,思维)
这个大佬的题解写得通俗易懂,看了他的题解终于搞明白了。
主要讲第二个操作,设\({\rm lowbit(x)}=2^k\),对于\(i-l<2^k\),\(a\bigoplus(x + i-l)=a\bigoplus x \bigoplus (i-l)\),相当于给区间\([l,l+2^k)\)异或上\(x\),在依次异或\(0,1,2,...,2^k-1\),用标记\(b_{l,k}\)代表后一个操作,随后\(l\to l+2^k,x \to x+2^k\),直到\(l>r\)。最后还要\(2^k\)从大到小填充最后一段未处理的区间。
对于标记\(b_{l,k}\),异或\(0,1,2,...,2^k-1\)等价于异或\(0,1,2,...,2^{k-1}-1,2^{k-1}|0, 2^{k-1}|1, ..., 2^{k-1}|(2^{k-1}-1)\),即给区间\([l+2^{k-1},l+2^{k})\)异或上\(2^{k-1}\),然后标记分裂成\(b_{l,k-1}\)和\(b_{l+2^{k-1},k-1}\)。
\(k\)最大只要开到\(\log(n)\),与异或的值域无关。
注意使用bitset,这题卡空间。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 6e5 + 10;
const double eps = 1e-5;
int arr[N];
bitset<25> tag[N];
int pw[N];
void upd(int l, int r, int x) {
for(int i = 0; i <= 20; i++) {
if((x & pw[i]) && (l + pw[i] - 1 <= r)) {
arr[l] ^= x;
arr[l + pw[i]] ^= x;
tag[l][i].flip();
l += pw[i];
x += pw[i];
}
}
for(int i = 20; i >= 0; i--) {
if(l + pw[i] - 1 <= r) {
arr[l] ^= x;
arr[l + pw[i]] ^= x;
tag[l][i].flip();
l += pw[i];
x += pw[i];
}
}
}
void solve(int n) {
for(int i = 20; i > 0; i--) {
for(int j = 1; j <= n; j++) {
if(tag[j][i]) {
tag[j][i - 1].flip();
if(j + pw[i - 1] <= n) {
arr[j + pw[i - 1]] ^= pw[i - 1];
tag[j + pw[i - 1]][i - 1].flip();
}
if(j + pw[i] <= n) arr[j + pw[i]] ^= pw[i - 1];
}
}
}
}
int main() {
IOS;
pw[0] = 1;
for(int i = 1; i <= 30; i++) pw[i] = (pw[i - 1] << 1);
int n, q;
cin >> n >> q;
for(int i = 1; i <= n; i++) {
cin >> arr[i];
}
for(int i = n; i >= 1; i--) arr[i] ^= arr[i - 1];
while(q--) {
int ty, l, r, x;
cin >> ty >> l >> r >> x;
if(ty == 0) {
arr[l] ^= x;
arr[r + 1] ^= x;
} else {
upd(l, r, x);
}
}
solve(n);
for(int i = 1; i <= n; i++) {
arr[i] ^= arr[i - 1];
cout << arr[i] << " \n"[i == n];
}
}