2018-2019 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) 题解

题目链接:点击传送

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));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值