2023省选武汉联测4

T1 挑战NPC

我们不对每个 \(d\) 分别求解答案,而是考虑枚举一个点集 \(S\) ,用 \(S\) 中距离最远的点的距离 \(dis\) 来更新 \(ans_{dis}\) 之后对 \(ans\) 数组进行一次前缀 \(\max\) 即可。容易发现本质不同的 \(S\) 中距离最远点的距离只有 \(n^2\) 中,因此考虑枚举两个点 \(a,b\) ,假设 \(dis(a,b)\)\(S\) 集合中距离最远的点,枚举所有点并将 \(dis(i,a)\le dis(a,b)\ and\ dis(i,b)\le dis(a,b)\) 的点加入到 \(S\) 中,考虑对 \(S\) 集合中的点求解最大团,一个经典的结论是原图最大团为其补图的最大独立集,因此我们建立原图的补图,求解补图的最大独立集,具体的 \((i,j)\) 之间存在连边当且仅当 \(dis(i,j)>dis(a,b)\) ,发现此时形成的图是二分图,有经典结论:二分图最大独立集等于总点数减最大匹配数。直接建图跑网络流即可。

简单证明一下补图为什么是二分图,考虑两个点 \(A,B\) ,如下图:

容易发现 \(S\) 集合中的点一定位于这两个圆的交集中,颜色相同的点之间不存在连边,颜色不同的点之间可能存在连边,得证。

code

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int max1 = 100, max2 = 1e5;
const int inf = 0x3f3f3f3f;
int n, m, x[max1 + 5], y[max1 + 5], dis[max1 + 5][max1 + 5];
int tmp[max1 + 5], len;
int ans[max2 + 5];
struct Node
	{ int next, v, flow; } edge[max1 * max1 + 5];
int head[max1 + 5], now[max1 + 5], total;
vector <int> tmp_edge[max1 + 5];
int color[max1 + 5];
int s, t, deep[max1 + 5], que[max1 + 5], l, r;
void Add ( int u, int v, int flow )
{
	edge[++total].v = v;
	edge[total].flow = flow;
	edge[total].next = head[u];
	head[u] = total;
	return;
}
void Add_Edge ( int u, int v, int flow )
{
	Add(u, v, flow);
	Add(v, u, 0);
	return;
}
int Ceil_Sqrt ( long long x )
{
	int l = 0, r = 1e6, res = 0;
	while ( l <= r )
	{
		int mid = l + r >> 1;
		if ( 1LL * mid * mid >= x )
			res = mid, r = mid - 1;
		else
			l = mid + 1;
	}
	return res;
}
void Get_Color ( int now )
{
	for ( auto v : tmp_edge[now] )
	{
		if ( !color[v] )
		{
			color[v] = 3 - color[now];
			Get_Color(v);
		}
	}
	return;
}
bool Bfs ()
{
	for ( int i = 1; i <= t; i ++ )
		deep[i] = inf;
	deep[s] = 0;
	l = 1, r = 0;
	que[++r] = s;
	while ( l <= r )
	{
		int x = que[l++];
		now[x] = head[x];
		if ( x == t )
			return true;
		for ( int i = head[x]; i; i = edge[i].next )
		{
			int v = edge[i].v, flow = edge[i].flow;
			if ( flow > 0 && deep[v] == inf )
			{
				deep[v] = deep[x] + 1;
				que[++r] = v;
			}
		}
	}
	return false;
}
int Dfs ( int x, int sum )
{
	if ( x == t )
		return sum;
	int res = 0;
	for ( int i = now[x]; i && sum; i = edge[i].next )
	{
		int v = edge[i].v, flow = edge[i].flow;
		if ( flow > 0 && deep[v] == deep[x] + 1 )
		{
			int k = Dfs(v, min(sum, flow));
			if ( !k )
				deep[v] = inf;
			else
			{
				edge[i].flow -= k;
				if ( i & 1 )
					edge[i + 1].flow += k;
				else
					edge[i - 1].flow += k;
				sum -= k;
				res += k;
			}
		}
	}
	return res;
}
void Solve ( int A, int B )
{
	int max_dis = dis[A][B];
	if ( max_dis > m )
		return;
	len = 0;
	tmp[++len] = A;
	tmp[++len] = B;
	for ( int i = 1; i <= n; i ++ )
		if ( i != A && i != B && dis[i][A] <= max_dis && dis[i][B] <= max_dis )
			tmp[++len] = i;
	total = 0;
	for ( int i = 1; i <= len + 2; i ++ )
		head[i] = 0, tmp_edge[i].clear(), color[i] = 0;
	for ( int i = 1; i <= len; i ++ )
		for ( int j = 1; j <= len; j ++ )
			if ( i != j && dis[tmp[i]][tmp[j]] > max_dis )
				tmp_edge[i].push_back(j);
	for ( int i = 1; i <= len; i ++ )
	{
		if ( !color[i] )
		{
			color[i] = 1;
			Get_Color(i);
		}
	}
	for ( int i = 1; i <= len; i ++ )
		for ( int j = 1; j <= len; j ++ )
			if ( color[i] < color[j] && dis[tmp[i]][tmp[j]] > max_dis )
				Add_Edge(i, j, 1);
	s = len + 1, t = len + 2;
	for ( int i = 1; i <= len; i ++ )
	{
		if ( color[i] == 1 )
			Add_Edge(s, i, 1);
		else
			Add_Edge(i, t, 1);
	}
	int res = 0;
	while ( Bfs() )
		res += Dfs(s, inf);
	res = len - res;
	ans[max_dis] = max(ans[max_dis], res);
	return;
}
int main ()
{
	freopen("challenge.in", "r", stdin);
	freopen("challenge.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for ( int i = 1; i <= n; i ++ )
		scanf("%d%d", &x[i], &y[i]);
	for ( int i = 1; i <= n; i ++ )
		for ( int j = 1; j <= n; j ++ )
			dis[i][j] = Ceil_Sqrt(1LL * ( x[i] - x[j] ) * ( x[i] - x[j] ) + 1LL * ( y[i] - y[j] ) * ( y[i] - y[j] ));
	ans[0] = 1;
	for ( int i = 1; i <= n; i ++ )
		for ( int j = i + 1; j <= n; j ++ )
			Solve(i, j);
	for ( int i = 1; i <= m; i ++ )
		ans[i] = max(ans[i], ans[i - 1]);
	for ( int i = 0; i <= m; i ++ )
		printf("%d ", ans[i]);
	printf("\n");
	return 0;
}

T2 糖果大赛

发现先手获胜的局面很多,因此考虑求解先手失败的局面。

容易发现一种数字我们不关心其出现的次数,因此我们用一个不可重集合维护当前序列中出现了哪些数,比较显然的是 \(\{1\}\)\(\{2\}\) 一定是先手失败, \(\{1,2\}\) 是先手获胜,考虑推测 \(\max(S)\ge 3\) 的情况,假设 \(S\) 中存在奇数,先手可以令 \(w=2\) 进行操作,容易发现先手必胜,于是考虑 \(S\) 中只存在偶数的情况,假设 \(S\) 中存在 \(\operatorname{mod} 4=2\) 的数,先手可以令 \(w=4\) 进行操作,容易发现此时先手必胜,因此考虑 \(S\) 中只存在 \(4\) 的倍数的情况,比较不幸的是,当 \(S\) 中存在 \(\operatorname{mod} 8=4\) 的数时,我们不能令 \(w=8\) 来构造,后手显然可以令 \(w=3\) 来获得必胜局面,但是容易发现当 \(S\) 中存在 \(\operatorname{mod} 3=1\)\(\operatorname{mod} 3=2\) 的数中的一种,先手必胜,因此先手失败的必要条件为同时存在 \(\operatorname{mod} 3=1\)\(\operatorname{mod} 3=2\) 的数或者 \(S\) 中均为 \(3\) 的倍数。

将两种条件简单结合,先手必败的第一种情况为 \(S\) 中存在 \(4\) 的倍数并且同时存在 \(\operatorname{mod} 3=1\)\(\operatorname{mod} 3=2\) 的数,打表发现必败局面只存在 \({4,8}\) ,当集合中元素更大时,先手可以令 \(w=12\) 得到必胜局面。

第二种情况为 \(S\) 中元素均为 \(12\) 的倍数,由于 \(a_i\le 200\) ,此时只存在 \(16\) 种不同的数,暴力求解必败状态用状压求解方案数即可。

code

#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
const int max1 = 200, max2 = 16, max3 = 1 << 16;
const int mod = 998244353;
int n, a[max1 + 5], sum;
int b[max2 + 5];
bool f[max3 + 5];
map < vector <int>, bool > Map;
int g[max1 + 5][max3 + 5];
void Add ( int &x, int y )
{
    x = x + y;
    if ( x >= mod )
        x = x - mod;
    return;
}
bool Dfs ( const vector <int> &now )
{
    if ( now.empty() )
        return true;
    else if ( Map.find(now) != Map.end() )
        return Map[now];
    vector <int> tmp;
    int Max = 0;
    for ( auto val : now )
        Max = max(Max, val);
    for ( int i = 1; i <= Max; i ++ )
    {
        tmp.clear();
        for ( auto v : now )
            if ( v % i )
                tmp.push_back(v % i);
        sort(tmp.begin(), tmp.end());
        tmp.resize(unique(tmp.begin(), tmp.end()) - tmp.begin());
        if ( !Dfs(tmp) )
        {
            Map[now] = true;
            return true;
        }
    }
    Map[now] = false;
    return false;
}
int main ()
{
    freopen("candy.in", "r", stdin);
    freopen("candy.out", "w", stdout);
    scanf("%d", &n);
    sum = 1;
    for ( int i = 1; i <= n; i ++ )
        scanf("%d", &a[i]), sum = 1LL * sum * a[i] % mod;
    int delta = 1;
    for ( int i = 1; i <= n; i ++ )
        delta = 1LL * delta * ( a[i] >= 1 ) % mod;
    sum = ( sum - delta + mod ) % mod;
    delta = 1;
    for ( int i = 1; i <= n; i ++ )
        delta = 1LL * delta * ( a[i] >= 2 ) % mod;
    sum = ( sum - delta + mod ) % mod;
    delta = 1;
    for ( int i = 1; i <= n; i ++ )
        delta = 1LL * delta * ( ( a[i] >= 8 ) + ( a[i] >= 4 ) ) % mod;
    sum = ( sum - delta + mod ) % mod;
    delta = 1;
    for ( int i = 1; i <= n; i ++ )
        delta = 1LL * delta * ( a[i] >= 4 ) % mod;
    sum = ( sum + delta ) % mod;
    for ( int i = 1; i <= n; i ++ )
        delta = 1LL * delta * ( a[i] >= 8 ) % mod;
    sum = ( sum + delta ) % mod;
    for ( int i = 1; i <= max2; i ++ )
        b[i] = i * 12;
    vector <int> tmp;
    for ( int s = 0; s < max3; s ++ )
    {
        tmp.clear();
        for ( int i = 1; i <= max2; i ++ )
            if ( s >> i - 1 & 1 )
                tmp.push_back(b[i]);
        f[s] = Dfs(tmp);
    }
    g[0][0] = 1;
    for ( int i = 1; i <= n; i ++ )
        for ( int s = 0; s < max3; s ++ )
            for ( int k = 1; k <= max2; k ++ )
                if ( b[k] <= a[i] )
                    Add(g[i][s | 1 << k - 1], g[i - 1][s]);
    for ( int s = 0; s < max3; s ++ )
        if ( !f[s] )
            Add(sum, mod - g[n][s]);
    printf("%d\n", sum);
    return 0;
}

T3 聚会

首先需要知道一些简单的结论???

结论一:

翻转的区间两两有交。

假设没有交集,简单画图发现此时一定不优。

结论二:

设翻转前第 \(i\) 个位置覆盖次数为 \(a_i\) ,翻转后第 \(i\) 个位置覆盖的次数为 \(b_i\) ,设翻转的区间的交集为 \([x,y]\)\([x,y]\)\(b_i\) 最大的位置记为 \(t\) ,可以观察发现:

\(b_t\ge \max(b)-1\) ,考虑反证,如果 \(b_t\le \max(b)-2\) ,此时找到一个 \(l=x\) 的区间和一个 \(r=y\) 的区间,容易发现不翻转这两个区间一定更优,反复进行这种操作,容易发现最终 \(b_t\ge \max(b)-1\)

\(a_t\ge \max(a)\) ,仍然考虑反证,容易发现 \(\max(a)\) 不可能在 \([x,y]\) 区间内被取到,否则 \(b_t\) 一定不是区间内最大值,因此至少存在一个区间没有覆盖到 \(\max(a)\) ,设 \(\max(a)\) 被取到的位置为 \(k\) ,那么有 \(a_t-b_t-1\ge a_k-b_k\) ,简单移项发现 \(b_k-b_t\ge 2\) 与上一条矛盾。

考虑如何处理原问题,首先一个初步的想法是二分 \(\max(b)\)\(lim\) ,由于 \(b_t\ge \max(b)-1\) ,因此我们讨论 \(b_t=\max(b)-1\)\(b_t=\max(b)\) ,设翻转的区间个数为 \(sum\) ,容易发现 \(b_t-a_t=sum\) ,因此我们可以直接求出 \(sum\) 的数值,考虑进行 Check ,由于 \(\max(a)\) 所在的位置一定位于 \([x,y]\) 内,因此我们考虑将区间按照 \(l\) 进行排序,依次扫左端点位于 \([1,t]\) 内所有的区间,假设当前扫到了第 \(i\) 个位置,设之前翻转的所有区间个数为 \(cnt\) ,容易发现我们还需要翻转 \(max(0,\lceil\tfrac{a_i+sum-lim}{2}\rceil-cnt)\) 个区间,为了尽量减少对右区间的影响,我们贪心的选择右端点最靠右的翻转即可。

code

#include <cstdio>
#include <vector>
#include <utility>
#include <algorithm>
#include <queue>
using namespace std;
const int max1 = 2e5;
int n, m;
long long A[max1 + 5], B[max1 + 5];
vector < pair <int, int> > bin[max1 + 5];
priority_queue < pair <int, int>, vector < pair <int, int> >, less < pair <int, int> > > que;
bool Check ( int pos, long long lim, long long sum )
{
	while ( !que.empty() )
		que.pop();
	for ( int i = 0; i <= n; i ++ )
		B[i] = 0;
	long long cnt = 0;
	for ( int i = 1; i <= pos; i ++ )
	{
		for ( auto val : bin[i] )
			if ( val.first > pos )
				que.push(val);
		long long k = ( A[i] + sum - lim + 1 ) / 2;
		while ( cnt < k )
		{
			if ( que.empty() )
				return false;
			pair <int, int> now = que.top();
			que.pop();
			long long del = min(1LL * now.second, k - cnt);
			cnt += del;
			now.second -= del;
			B[pos + 1] -= del;
			B[now.first] += del << 1;
			if ( now.second )
				que.push(now);
		}
	}
	for ( int i = pos + 1; i <= n; i ++ )
	{
		B[i] += B[i - 1];
		if ( A[i] + B[i] > lim )
			return false;
	}
	return true;
}
int main ()
{
	freopen("party.in", "r", stdin);
	freopen("party.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for ( int i = 1, a, b, c; i <= m; i ++ )
	{
		scanf("%d%d%d", &a, &b, &c);
		if ( a > b )
			swap(a, b);
		A[a] += c;
		A[b] -= c;
		bin[a].push_back(make_pair(b, c));
	}
	int pos = 0;
	for ( int i = 1; i <= n; i ++ )
	{
		A[i] += A[i - 1];
		if ( A[pos] < A[i] )
			pos = i;
	}
	long long l = 1, r = A[pos], ans = A[pos];
	while ( l <= r )
	{
		long long mid = l + r >> 1;
		if ( Check(pos, mid, A[pos] - mid) || Check(pos, mid, A[pos] - mid + 1) )
			ans = mid, r = mid - 1;
		else
			l = mid + 1;
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2023-04-20 20:59  KafuuChinocpp  阅读(29)  评论(0)    收藏  举报