题目链接:点击传送
B.Coprime Integers
最入门的莫比乌斯反演,没啥好讲的,推荐入门博客 莫比乌斯反演
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e7 + 10;
int vis[maxn], pri[maxn], mu[maxn], cnt;
void init(int n) {
mu[1] = 1;
for (int i = 2; i <= n ; i++) {
if (!vis[i])
pri[++cnt] = i, mu[i] = -1;
for (int j = 1; i * pri[j] <= n; j++) {
vis[pri[j] * i] = 1;
if (i % pri[j])
mu[i * pri[j]] = -mu[i];
else
break;
}
}
for (int i = 2; i <= n; i++)
mu[i] += mu[i - 1];
}
ll gao(int n, int m) {
if (!n || !m)
return 0;
ll ans = 0;
if (n > m)
swap(n, m);
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += 1ll * (mu[r] - mu[l - 1]) * (n / l) * (m / l);
//printf("ans = %d\n", ans);
}
return ans;
}
int main () {
int a, b, c, d;
init(1e7);
cin>>a>>b>>c>>d;
ll ans = 0;
ans = gao(b, d) - gao(a - 1, d) - gao(b, c - 1) + gao(a - 1, c - 1);
cout<<ans;
}
C.Contest Setting
设d[i][j]为前 i 种数去了 j 种的方案数,我们用一个mp记录每种数的数量,那么对于都 i 种数,我如果不取这种数,那么d[i][j] = d[i - 1][j],如果取这种数,那么d[i][j] += d[i-1][j] * mp[a[i]]。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 998244353;
ll d[1010][1010];
int a[1010];
map<int, int> mp;
void add(ll &x, ll y) {
x += y;
while (x >= mod)
x -= mod;
}
int main () {
int n, k;
cin>>n>>k;
for (int i = 1; i <= n; i++)
cin>>a[i], mp[a[i]]++;
sort(a + 1, a + 1 + n);
n = unique(a + 1, a + 1 + n) - a - 1;
for (int i = 1; i <= n; i++) {
d[i - 1][0] = 1;
for (int j = 1; j <= i; j++) {
add(d[i][j], d[i - 1][j] + d[i - 1][j - 1] * mp[a[i]] % mod);
}
}
cout<<d[n][k]<<endl;
}
D.Count The Bits
设f[i][j] 为 0 到 2^i 中%k = j 的数的个数,d[i][j] 为 0 到 2^i 中 %k = j 的数的二进制1的个数,那么答案就是求d[n][0],对于f[i][j],我们可以在最高位取0:f[i][j] = f[i-1][j],最高位取1:f[i][j] += f[i - 1][(j - 2^i ) % k],后面这个转移没看懂?我们知道最高位取1的二进制数可以表示为1XXXX,XXXX记录了d[i - 1][j]的所有信息,那么令 j = 2^i % k + XXXX % k,XXXX % k = j - 2^i % k。ok,我们来转移d数组,同样的,最高位为0,d[i][j] = d[i - 1][j],最高位为1:d[i][j] += d[i -1][(j - 2^i) % k],但是还少了点什么,我们最高位的1没计算来,那么我们有多少个最高位为1且 %k = j 的数呢?显然是f[i - 1][(j - 2^i) % k],d[i][j]再加上这个,就正确了。我代码用了滚动数组
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1010, mod = 1e9 + 9;
ll d[2][maxn] ,f[130][maxn];
void add(ll &x, ll y) {
x = (x + y) % mod;
}
int gao(int x) {
if (!x)
return 0;
return gao(x / 2) + (x % 2);
}
int main () {
int n, k, p = 1, m = 1, s = 1, cur = 0;
cin>>k>>n;
for (m = 1; m < k && s < n; m <<= 1)
p = p * 2 % k, s++;
for (int i = 0; i < m && (i < (1 << s)); i++) {
d[cur][i % k] += gao(i);
f[cur][i % k]++;
}
for (int i = s; i <= n; i++) {
cur = !cur;
for (int j = 0; j < k; j++) {
d[cur][j] = d[!cur][j];
int q = (j - p + k) % k;
add(d[cur][j], d[!cur][q] + f[!cur][q]);
f[cur][j] = f[!cur][j];
add(f[cur][j], f[!cur][q]);
}
p = p * 2 % k;
}
cout<<d[cur][0];
}
E.Cops And Roobers
我们要花费最少的钱去阻碍B,使得B不能走出边界,其实就是求最小割,我们把每个点 u 拆为入点 u1,出点u2,连接u1–>u2,流量无穷大,首先源点S连接B入点,流量无限大,所有边界点的出点连接汇点T,如果该边界点是普通点,流量设为无限大,否则设为题目给的权值,对于所有相邻的点u,v,连接u2–>v1,流量根据题目设无穷大或者给定权值,然后跑最大流,如果最大流为无限大,那么无解,否则最大流就是最小割即答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2010;
const int inf=1e9;
struct Edge
{
int from,to,cap,flow;
};
struct Dinic
{
int n,m,s,t;
vector<Edge>edges;
vector<int>G[maxn];
bool vis[maxn];
int cur[maxn],d[maxn];
void Addedge(int from,int to,int cap)
{
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});
m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
void init(int n)
{
this->n=n;
edges.clear();
for(int i=0;i<maxn;i++)
G[i].clear();
}
bool bfs()
{
memset(vis,0,sizeof(vis));
queue<int>Q;
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty())
{
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++)
{
Edge& e=edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow)
{
vis[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}
int dfs(int x,int a)
{
if(a==0||x==t)return a;
int flow=0,f;
for(int& i=cur[x];i<G[x].size();i++)
{
Edge& e=edges[G[x][i]];
if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0)
{
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)
break;
}
}
return flow;
}
int maxflow(int s,int t)
{
this->s=s,this->t=t;
int flow=0;
while(bfs())
{
memset(cur,0,sizeof(cur));
flow+=dfs(s, inf);
}
return flow;
}
}solve;
char s[35][35];
int n, m, c[26];
int id(int i, int j) {
return (i - 1) * m + j;
}
int gao(int i, int j, int T) {
int flow = inf;
if (s[i][j] != '.' && s[i][j] != 'B')
flow = c[s[i][j] - 'a'];
if (T && T <= n * m)
T += n * m;
solve.Addedge(id(i, j), T, flow);
}
int main()
{
int k;
cin>>n>>m>>k;
swap(n, m);
int S = 0, T = n * m * 2 + 1;
solve.init(T + 1);
for (int i = 1; i <= n; i++)
cin>>s[i] + 1;
for (int i = 0; i < k; i++)
cin>>c[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i][j] == 'B')
solve.Addedge(S, id(i, j) + n * m, inf);
int flow = inf;
if (s[i][j] != '.' && s[i][j] != 'B')
flow = c[s[i][j] - 'a'];
solve.Addedge(id(i, j) + n * m, id(i, j), flow);
}
gao(i, 1, T);
if (m != 1)
gao(i, m, T);
if (i == 1)
for (int j = 2; j < m; j++)
gao(i, j, T);
else if (i == n)
for (int j = 2; j < m; j++)
gao(i, j, T);
}
for (int i = 1; i < n; i++)
for (int j = 1; j < m; j++) {
gao(i, j, id(i, j + 1));
gao(i, j, id(i + 1, j));
gao(i, j + 1, id(i, j));
gao(i + 1, j, id(i, j));
}
int ans = solve.maxflow(S, T);
if (ans == inf)
puts("-1");
else
cout<<ans;
}
F.Rectangles
赛场上写了个假的扫描线,T飞了,我们把每个矩形拆成两条边(上面的边和下面的边)存起来排序,然后枚举每条边,sum线段树记录区间更新奇数次的线段的总长,线段树区间更新很简单,假设我要把边 i 的两端点更新到线段树的中去,我们找到这样的合法区间[l, r],这个区间长度为X[r] - X[l-1],现在更新这个区间,以前的更新次数为偶数的线段总长变成了现在奇数的总长,那么sum[o] = X[r] - X[l - 1] - sum[o],然后ans += (边i+1的高度 - 边 i 的高度)* 线段树中所有更新次数为奇数的线段总长。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
int sum[maxn * 4], tag[maxn * 4];
struct node {
int l, r, h;
bool operator<(const node& t) const {
return h < t.h;
}
} a[maxn];
int X[maxn];
void up(int o, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
sum[o] = X[r] - X[l - 1] - sum[o];
tag[o] ^= 1;
return;
}
int m = (l + r) / 2, ls = o * 2, rs = o * 2 + 1;
if (tag[o]) {
sum[ls] = X[m] - X[l - 1] - sum[ls];
sum[rs] = X[r] - X[m] - sum[rs];
tag[ls] ^= tag[o]; tag[rs] ^= tag[o];
tag[o] = 0;
}
if (ql <= m)
up(ls, l, m, ql, qr);
if (qr > m)
up(rs, m + 1, r, ql, qr);
sum[o] = sum[ls] + sum[rs];
}
int main() {
int n, x1, y1, x2, y2, sz = 0;
cin>>n;
for (int i = 1; i <= n; i++) {
cin>>x1>>y1>>x2>>y2;
a[i].l = x1, a[i].r = x2;
a[i].h = y1;
a[i + n] = a[i];
a[i + n].h = y2;
X[++sz] = x1;
X[++sz] = x2;
}
sort(a + 1, a + 1 + n * 2);
sort(X + 1, X + 1 + sz);
sz = unique(X + 1, X + 1 + sz) - X - 1;
ll ans = 0;
for (int i = 1; i <= n * 2; i++) {
int l = lower_bound(X + 1, X + 1 + sz, a[i].l) - X;
int r = lower_bound(X + 1, X + 1 + sz, a[i].r) - X;
up(1, 1, sz, l + 1, r);
if (i < n * 2)
ans += 1ll * sum[1] * (a[i + 1].h - a[i].h);
}
cout<<ans;
}
H.Repeating Goldbachs
先线性筛或欧拉筛筛素数后,暴力求n即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 10, mod = 1e6 + 10;
int vis[maxn];
void init(int n) {
for (int i = 2; i <= n; i++)
if (!vis[i]) {
for (int j = i * 2; j <= n; j += i)
vis[j] = 1;
}
}
int gao(int n) {
if (n < 4)
return 0;
if (n == 4)
return 1;
for (int i = 3; ;i++)
if (!vis[i] && !vis[n - i])
return gao(n - i - i) + 1;
}
int main () {
init(1e6);
int n;
cin>>n;
printf("%d\n", gao(n));
}