2022 高考集训

Updating!

虽然说题目叫 2022 高考集训然而我并不集训 233,我只负责补题 QwQ

fengwu 大爷说不要放题面,遂不放了 .

没代码的就是口胡,不用管就行 .

缺省源大概是

#include <bits/stdc++.h>
template<typename T>
inline T chkmin(T& x, const T& y){if (x > y) x = y; return x;}
template<typename T>
inline T chkmax(T& x, const T& y){if (x < y) x = y; return x;}
#define file(x) {freopen(x".in", "r", stdin); freopen(x".out", "w", stdout);}
#define twicecat(p, q) p##p##q 
#define twiceline(t) twicecat(_, t) 

Day 1

A. 2A

模拟即可,先扫一遍给出的字符串,然后提出四个数字来就好做了 .

如果满足格式就等价于改完的 IP 和原来的一模一样 .

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
string s; 
int main()
{
	file("ip");
	cin >> s; int len = s.length();
	int num[4] = {}, cc = 0;
	for (int i=0; i<len; i++)
		if (isdigit(s[i])){num[cc] = num[cc] * 10 + s[i] - '0'; chkmin(num[cc], 255);}
		else if (i && isdigit(s[i-1])) ++cc;
	char ans[114514];
	sprintf(ans, "%d.%d.%d.%d", num[0], num[1], num[2], num[3]);
	if (!strcmp(ans, s.c_str())) puts("YES");
	else puts("NO"), cout << ans << endl;
	return 0;
}

B. 2B

非常显然的一个贪心,因为要最后留下的字符最少,所以肯定要多做操作 .

于是我们尽量多做 AP 再做 PP,然而我们注意到字符集是 \(\{\mathtt A,\mathtt P\}\),于是这个操作其实相当于把 P 前面的字符删掉,所以扫一遍整个字符串,用一个栈维护,遇到 P 就弹即可 .

(其实这里栈可以省掉,就等价于 Eafoo 的 DP 做法了)

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
string s;
stack<char> S;
int main()
{
	file("apstr");
	cin >> s; int n = s.length(); s = "$" + s;
	for (int i=1; i<=n; i++)
	{
		if ((s[i] == 'P') && (!S.empty())) S.pop();
		else S.push(s[i]);
	} printf("%d", int(S.size())); 
	return 0;
}

C. 2C

wobuhuizuo

D. 2D

应该确保 \(k<0\) .

(结果样例 \(k=2\),迫真)

开始胡的是从 \(k\) 小的往 \(k\) 大的推,这样不太能做,等价于 Eafoo 的 \(n^2\) 过百万 拓扑排序算法 .

开始复读题解:

首先 \((k+1)\)-degree 一定是 \(k\)-degree 的子图 .

于是我们从大到小枚举 \(k\),每次扩展原图即可 .

如果一个点属于 \((k+1)\)-degree 但不属于 \(k\)-degree 我们就称其在第 \(k+1\) 层,记 \(D(x)\)\(x\) 的层数 .

我们按照层数从大到小加入点,考虑计算每次加入点引起的分数贡献 .

首先肯定是分顶点数 \(n\),边数 \(m\),边界边数 \(b\) 计算贡献 .

在点集上定义一个偏序 \(\prec\)(对应到题解中就是给每个点一个 \(rank\)),满足:

  • \(D(x)<D(y)\),则 \(x\prec y\) .
  • \(D(x)=D(y)\)\(x<y\),则 \(x\prec y\) .

可以看出,这就是层数的比较 . 另一方面,这是双关键字排序,可以通过 计数排序 桶排序(也就是所谓 bin-sort)完成 .

于是我们可以通过边上两点 \(\prec\) 来对一条边进行分类:

  • \(E(u,\prec)\) 表示所有边 \(u\to v\) 中满足 \(u\prec v\) 的个数 .
  • \(E(u,\succ)\) 表示所有边 \(u\to v\) 中满足 \(v\prec u\) 的个数 .
  • \(E(u,=)\) 表示所有边 \(u\to v\) 中不满足上面两种情况的个数(等价于 \(D(u)=D(v)\)) .

于是每次加一个点 \(u\),给 \(n,m,b\) 的影响分别是:

  • \(\Delta n=1\) .
  • \(\Delta m = E(u,\prec)+\dfrac 12E(u,=)\) .
  • \(\Delta b = E(u,\prec)-E(u,\succ)\) .

然而实现的时候可以不用这么麻烦 就这样从小到大加,统计 \(score\) 然后取 min 即可 .

然后做的时候子图还可能被边连起来(合并),所以还需要并查集维护一下 .

关于时间复杂度,题解说是 \(O(n+m)\),感觉如果题解您真做到这个复杂度不得图灵奖,,,肯定要乘个反 Ackermann 函数吧 233

太难写了,SMYwy 大佬的做法我没看懂,于是贺 lyin 的代码跑路了 .

说的不够完善的地方结合代码理解吧 .

lyin 大佬的代码
#include <bits/stdc++.h>
#define fre(x) freopen( #x ".in", "r", stdin), freopen( #x ".out", "w", stdout )
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double db; typedef long double ldb;
const int N = 1e6 + 10;

mt19937 mt( (ull)(new char) );
int Rand ( int l, int r ) { return uniform_int_distribution<>(l,r)(mt); }

int n, m, kn, km, kb, du[N]; vector <int> vec[N];

struct BCJ
{
	int fa[N], sm[N], sn[N], sd[N];

	void Init ( int n ) { for( int i = 1; i <= n; ++i) fa[i] = i, sm[i] = 0, sn[i] = 1, sd[i] = du[i]; }

	int Find ( int x ) { return x == fa[x] ? x : fa[x] = Find(fa[x]); }

	void Merge ( int x, int y )
	{
		int fx = Find(x), fy = Find(y); if( fx == fy ) return; if( sn[fx] > sn[fy] ) swap( fx, fy );
		fa[fx] = fy, sm[fy] += sm[fx], sn[fy] += sn[fx], sd[fy] += sd[fx];
	}

	ll Calc ( int x ) { return sn[x] == 1 ? -LLONG_MAX : 0LL + 1LL * km * sm[x] + 1LL * kn * sn[x] + 1LL * kb * ( sd[x]-2LL*sm[x] ); }
}S;

struct Graph
{
	struct Edge { int to, next; } E[N<<1];
	int ind, head[N];

	void Insert ( int u, int v ) { ++ind, ++du[u], E[ind].to = v, E[ind].next = head[u], head[u] = ind; }

	void Link ( int u, int v ) { Insert( u, v ), Insert( v, u ); }

	vector <int> bin[N]; bool vis[N]; int top;

	bool Select ( int k, int &u ) { while( bin[top].empty() && top <= k ) ++top; if( top > k ) return false; u = bin[top][ bin[top].size()-1 ], bin[top].pop_back(); return true; }

	void Solve ()
	{
		for( int i = 1; i <= n; ++i) bin[ du[i] ].push_back( i ); top = 0;
		for( int k = 1, u = 0; k <= n; ++k) while( Select( k, u ) )
		{
		 	if( vis[u] ) continue; vec[k].push_back(u), vis[u] = true, S.sm[u] = top;
			for( int i = head[u]; i; i = E[i].next ) if( !vis[ E[i].to ] ) bin[ --du[ E[i].to ] ].push_back( E[i].to ), top = min( top, du[ E[i].to ] );
		}
		for( int i = 1; i <= n; ++i) vis[i] = 0;
		ll maxi = -LLONG_MAX, maxk = 0;
		for( int k = n; k >= 1; --k)
		{
			reverse( vec[k].begin(), vec[k].end() );
			for( auto u : vec[k] ) { vis[u] = true; for( int i = head[u]; i; i = E[i].next ) if( vis[ E[i].to ] ) S.Merge( u, E[i].to ); }
			for( auto u : vec[k] ) if( maxi < S.Calc( S.Find(u) ) ) maxi = S.Calc( S.Find(u) ), maxk = k;
		}
		cout << maxk << " " << maxi << "\n";
	}
}G;

void Solve ()
{
	cin >> n >> m >> km >> kn >> kb, kn = -kn;
	for( int i = 1; i <= m; ++i) { int a, b; cin >> a >> b, G.Link( a, b ); }
	S.Init(n), G.Solve();
}

signed main ()
{
	fre(kdgraph);
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); Solve(); return 0;
}
我的代码
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
const int N = 1e6 + 233;
const ll INF = 0x7f7f7f7f7f7f7f7fll;
vector<int> g[N];
int d[N];
inline void addedge(int u, int v){g[u].emplace_back(v); ++d[u];}
inline void ade(int u, int v){addedge(u, v); addedge(v, u);}
int n, m, mm, nn, bb;
struct dsu
{
	int fa[N];
	ll dm[N], dn[N], dd[N];
	inline void reset(int n){for (int i=1; i<=n; i++){fa[i] = i; dm[i] = 0; dn[i] = 1; dd[i] = d[i];}}
	int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
	void merge(int x, int y)
	{
		x = get(x); y = get(y);
		if (x == y) return ;
		if (dn[x] > dn[y]) swap(x, y); //
		fa[x] = y; dm[y] += dm[x]; dn[y] += dn[x]; dd[y] += dd[x]; 
	}
	ll score(int x){x = get(x); return dn[x] == 1 ? -INF : dm[x] * mm + dn[x] * nn + (dd[x] - 2 * dm[x]) * bb;}
}D;
vector<int> lev[N], bin[N];
bool vis[N];
int top;
bool select(int k, int& u) // 找一个 k 层点 u
{
	while (bin[top].empty() && (top <= k)) ++top;
	if (top > k) return false;
	u = bin[top].back(); bin[top].pop_back();
	return true; 
}
inline void solve()
{
	D.reset(n); int u = 0;
	for (int i=1; i<=n; i++) bin[d[i]].emplace_back(i);
	for (int k=1; k<=n; k++)
		while (select(k, u))
		{
			if (vis[u]) continue;
			vis[u] = true;
			lev[k].emplace_back(u); D.dm[u] = top;
			for (int v : g[u])
				if (!vis[v]){bin[--d[v]].emplace_back(v); chkmin(top, d[v]);}
		}
	memset(vis, false, sizeof vis);
	ll maxn = -INF, k = 114514;
	for (int i=n; i>=1; i--)
	{
		reverse(lev[i].begin(), lev[i].end());
		for (int u : lev[i])
		{
			vis[u] = true;
			for (int v : g[u])
				if (vis[v]) D.merge(u, v);
		}
		for (int u : lev[i])
		{
			ll _ = D.score(u);
			if (maxn < _){maxn = _; k = i;}
		}
	} printf("%lld %lld\n", k, maxn);
}
int main()
{
	file("kdgraph");
	scanf("%d%d%d%d%d", &n, &m, &mm, &nn, &bb); nn *= -1;
	for (int i=0, u, v; i<m; i++) scanf("%d%d", &u, &v), ade(u, v);
	solve();
	return 0;
}

简直一模一样!!!说是 CV 的都不为过!!!!

Day 2

A. 交通

把每个点的两个出边、两个入边连边,则我们的要求就可以变成在新图上选 \(n\) 个不相邻的点 .

容易发现这个图每个点的度数都是 \(2\) 且只有偶环,于是答案就是 \(2^{cycle}\),其中 \(cycle\) 是环数(此时也是连通块个数),并查集即可 .

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 5e5 + 500, P = 998244353;
int n, tin[N], tout[N];
int qpow(int a, int n)
{
	int ans = 1;
	while (n)
	{
		if (n & 1) ans = 1ll * ans * a % P;
		a = 1ll * a * a % P; n >>= 1;
	} return ans;
}
struct dsu
{
	int fa[N];
	dsu(){iota(fa, fa+N, 0);}
	int get(int x)const{return x == fa[x] ? x : get(fa[x]);}
	inline void merge(int u, int v){fa[get(u)] = get(v);}
	inline int blocks()const
	{
		int ans = 0;
		for (int i=1; i<=n; i++) ans += (i == fa[i]);
		return ans;
	}
}D;
int main()
{
	file("a");
	scanf("%d", &n); n <<= 1; 
	for (int i=1, u, v; i<=n; i++)
	{
		scanf("%d%d", &u, &v);
		if (tin[v])  D.merge(i, tin[v]);
		if (tout[u]) D.merge(i, tout[u]);
		tin[v] = tout[u] = i;
	} printf("%d\n", qpow(2, D.blocks()));
	return 0;
}

B. 冒泡排序

观察题目这个冒泡 . 可以讨论 \(a_i\) 要往哪边冒,这样就把问题细化到点上了

\(p\) 是题目里给的 \(p\) 的置换逆 .

如果 \(a_i>i\),那么必须往右边冒,那么我们就要求后面的数必须后冒,于是就会发现相邻位置的交换顺序有 一些限制,限制形如某对相邻的交换必须在它旁边的相邻对交换之前 / 之后,\(a_i<i\) 也类似 .

这个可以大于小于分开处理就变成区间加求前缀和,差分即可 .

现在问题就是给一堆形如 \(p_i>p_{i-1}\)\(p_i<p_{i-1}\) 的限制,让你求满足条件的排列 \(\{p\}\) 的个数,直接 \(O(n^2)\) DP 即可 .

应该能做到更优复杂度 desu(题解说 容斥 + 分治 FFT 可以 \(O(n\log ^2n)\) .jpg)


具体说一下这个 DP,大概是排列 DP 的经典 Trick,有点像 地精部落 .

\(dp_{i,j}\) 表示处理到第 \(i\) 个数且有 \(j\) 个大于位置 \(i\) 放的数 .

顺推做法大概就是:如果 \(p_{i+1}>p_i\) 那么就有 \(j\) 种方案可以放在 \(i+1\)\(p_{i+1}<p_i\) 同理,无约束就直接把两种情况并起来,然后因为加的是区间所以差分优化一下就好了,\(O(n^2)\) .

逆推一样就是把差分换成前缀和,好实现一点,\(O(n^2)\) .

第一维可以滚一下,空间复杂度就是 \(O(n)\) 的了 .

UPD: DP 部分是 [HNOI2015] 实验比较 弱化版,并且原题存在 \(O(n^2)\) 做法 .

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 5555, P = 1e9 + 7;
int n, dp[2][N], a[N], b[N];
int main()
{
	file("mp");
	scanf("%d", &n);
	for (int i=1, x; i<=n; i++)
	{
		scanf("%d", &x); ++x;
		if (x == i){puts("0"); return 0;}
		if (x > i){++a[i]; --a[x-1];}
		else{++b[x]; --b[i-1];}
	} --n;
	for (int i=1; i<=n; i++)
	{
		a[i] += a[i-1]; b[i] += b[i-1];
		if (a[i] && b[i]){puts("0"); return 0;}
	}
	for (int i=1; i<=n; i++) dp[1][i] = 1;
	int lst = 1;
	for (int i=2; i<=n; i++)
	{
		bool _P = a[i-1], _Q = b[i-1], _R = !_P && !_Q, now = !lst;
		if (_P || _R) for (int j=2; j<=i; j++) dp[now][j] = (dp[now][j-1] + dp[lst][j-1]) % P;
		if (_Q || _R) for (int j=i-1; j; j--) dp[now][j] = (dp[now][j+1] + dp[lst][j]) % P;
		memset(dp[lst], 0, sizeof dp[lst]);
		lst ^= 1;
	} int ans = 0;
	for (int i=1; i<=n; i++) ans = (ans + dp[n&1][i]) % P;
	printf("%d\n", ans);
	return 0;
}

C. 矩阵

一道牛逼题,结论是只需要把前两行和前两列归零,如果此时还不能把整个矩阵归零那么无解 .

虽然我并不会证 desu(UPD:GCC 定理

这样就随便做了 .

参考代码

蒯的 std

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1234;
int n, m, a[N][N];
ll line[N], row[N], diag[2 * N];
int main()
{
	file("c");
	scanf("%d%d", &n, &m);
	for (int i=1; i<=n; i++)
		for (int j=1; j<=m; j++) scanf("%d", a[i] + j);
	line[1] = a[2][2] - a[1][1]; diag[n] = -a[2][2];
	for (int i=2; i<=n; i++){diag[n-i+1] = -(a[i][1] + line[i]); line[i+1] = -(a[i+1][2] + diag[n-i+1]);}
	for (int i=2; i<=m; i++){diag[n+i-1] = -(a[1][i] + line[1] + row[i]); row[i+1] = -(a[2][i+1] + diag[n+i-1]);}
	for (int i=1; i<=n; i++)
		for (int j=1; j<=m; j++)
			if (a[i][j] + line[i] + row[j] + diag[j-i+n]){puts("-1"); return 0;}
	printf("%d\n", 2*(n+m) - 1);
	for (int i=1; i<=n; i++) printf("1 %d %lld\n", i, line[i]);
	for (int i=1; i<=m; i++) printf("2 %d %lld\n", i, row[i]);
	for (int i=1; i<=n+m-1; i++) printf("3 %d %lld\n", i-n, diag[i]);
	return 0;
}

D. 花瓶

先口胡,代码以后补 .

比较显然的斜率优化 DP,类似 HDU2829 Lawrence .

然而这是一个 2D 的斜率优化 DP,这里总结一下 Trick .

\(s\) 是序列前缀和,令 \(dp_{i,j}\) 表示目前分到第 \(i\) 个花瓶,上一个段的右端点为 \(j\),于是转移为

\[dp_{i,j} = \max_k\{dp_{j,k}+(s_j-s_k)(s_i-s_j)\} \]

这样是 \(O(n^3)\) 的,斜率优化掉就是 \(O(n^2)\) 的了 .

然而 Keven_He 一眼秒了这个斜优,,,,囧

分析不想写了,看代码吧 . (不过要除变乘??)

好像所有人的代码都是一样的????疑似 SSH .

参考代码
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
const int N = 5555;
int n, q[N], id[N];
ll a[N], dp[N][N], nINF;
int main()
{
	file("d");
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%lld", a+i), a[i] += a[i-1], id[i] = i;
	stable_sort(id, id+1+n, [](const int& x, const int& y){return a[x] < a[y];});
	memset(dp, -0x3f, sizeof dp); nINF = dp[0][0];
	for (int i=0; i<=n; i++) dp[i][0] = 0;
	for (int i, j=1; j<=n; j++)
	{
		int head = 1, tail = 0;
		for (int _=0; _<=n; _++) // push
		{
			i = id[_];
			if (i >= j) continue;
			while ((head < tail) && (dp[j][q[tail - 1]] - dp[j][q[tail]]) * (a[q[tail]] - a[i]) <= (dp[j][q[tail]] - dp[j][i]) * (a[q[tail-1]] - a[q[tail]])) --tail;
            q[++tail] = i;
		}
		for (int _=n, k; _>=0; _--) // pop
		{
			i = id[_];
			if (i <= j) continue;
			while ((head < tail) && (dp[j][q[head]] - dp[j][q[head + 1]] <= (a[i] - a[j]) * (a[q[head]] - a[q[head + 1]]))) ++head;
            k = q[head];
            chkmax(dp[i][j], dp[j][k] + (a[i] - a[j]) * (a[j] - a[k]));
		}
	}
	ll ans = nINF;
	for (int i=0; i<=n; i++) chkmax(ans, dp[n][i]);
	printf("%lld\n", ans);
	return 0;
}

Day 3

简单打了一下,跟 GOODBOUNCE 似的 .

A. Watching Fireworks is Fun

原题链接:CF372C .

hzoi 翻译的题面太怪了,读错题直接 GG,然而样例都是 \(d=1\) 也看不出来 >_<

首先什么烟花一起放都是诈骗的,只需要考虑前一个烟花到后一个走的位移即可 .

朴素 DP 还是非常显然的 .

\(dp_{i,j}\) 为处理到第 \(i\) 个烟花,目前在 \(j\) 的答案,于是:

\[dp_{i,j}=b_i-|a_i-j|+\max_k\{dp_{i-1,k}\}\qquad k\in[\max\{1,j-\Delta d\},\min\{n,j+\Delta d\}] \]

其中 \(\Delta=t_i-t_{i-1}\) 是时间差 .

然后注意此时 DP 数组大小是 \(150000\times 300\times 64\text{ Byte}\approx 2746\text{ MiB}\) 的,显然是开不下的,必须要滚一维才行 .

然后这样暴力 DP 是 \(O(n^2m)\) 的,肯定过不去,考虑优化转移,首先看这个区间 \(\max\) 可以线段树或 ST 表优化,这样就是 \(O(nm\log n)\) 的,但是如果你写了就会发现这题点名卡带 \(\log\) 做法 XD .

首先忽略掉区间范围的 \(\min,\max\),就发现区间长度不变,加上 \(\min,\max\) 就只需要一些小的边界判断,单调队列优化即可做到 \(O(nm)\),可以通过此题 .

听说需要两次单调队列?不用吧 /kx

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 150514;
ll n, m, d, a[N], b[N], t[N], dp[N], nw[N], q[N];
int main()
{
	file("fire");
	scanf("%lld%lld%lld", &n, &m, &d);
	for (int i=1; i<=m; i++) scanf("%lld%lld%lld", a+i, b+i, t+i);
	for (int _=1; _<=m; _++)
	{
		int r = min((t[_] - t[_-1]) * d, n), head = 1, tail = 0;
		for (int i=1; i<=r; i++)
		{
			while ((head <= tail) && (dp[q[tail]] < dp[i])) --tail;
			q[++tail] = i;
		}
		for (int i=1; i<=n; i++)
		{
			if (i + r <= n)
			{
				while ((head <= tail) && (dp[q[tail]] < dp[i + r])) --tail;
				q[++tail] = i + r; 
			}
			while (q[head] < i - r) ++head;
			nw[i] = dp[q[head]];
		}
		for (int i=1; i<=n; i++) dp[i] = nw[i] + b[_] - abs(a[_] - i); 
	}
	ll ans = LLONG_MIN;
	for (int i=1; i<=n; i++) chkmax(ans, dp[i]);
	printf("%lld\n", ans);
	return 0;
}

B. Perform巡回演出

简单 DP .

\(dp_{i,j}\) 表示到了第 \(i\) 个点,走了 \(j\) 天,每次暴力往外扩展即可,\(O(n^2k)\) .

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 11, K = 1111;
int n, kkk;
ll dp[N][K];
vector<int> w[N][N];
inline void solve()
{
	for (int i=1; i<=n; i++)
		for (int j=1, sz; j<=n; j++)
		{
			if (i == j) continue;
			scanf("%d", &sz);
			for (int k=0, x; k<sz; k++) scanf("%d", &x), w[i][j].emplace_back(x);
		}
	memset(dp, 0x7f, sizeof dp);
	dp[1][0] = 0;
	for (int k=0; k<kkk; k++) 
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
			{
				if (i == j) continue;
				int sz = w[i][j].size(), val = w[i][j][k % sz];
				if (!val) continue;
				chkmin(dp[j][k+1], dp[i][k] + val);
			}
	if (dp[n][kkk] == dp[0][0]) puts("0");
	else printf("%lld\n", dp[n][kkk]);
}
int main()
{
	file("perform");
	while (~scanf("%d%d", &n, &kkk) && n && kkk) solve();
	return 0;
}

C. 枪战Maf

原题链接:[POI2008] MAF-Mafia .

这题还是非常好想的,就是细节比较多 .

首先每个人和他的 Aim 连边变成一个基环内向森林 .

最小就是环留下一个,基环树或者树就全干掉 .

最大就拓扑一下,每次把入度为 0 的点的出点干掉,整环直接环长除以二 .

时间复杂度 \(O(n)\) .

参考代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e6 + 500;
vector<int> g[N];
int n, to[N], in[N], leaf[N], minn, maxn;
bool cycle[N], color[N];
inline void bfs()
{
	queue<int> q;
	for (int i=1; i<=n; i++)
		if (!in[i]){q.push(i); ++minn; ++maxn;}
	while (!q.empty())
	{
		int u = q.front(); q.pop();
		cycle[u] = true; u = to[u];
		if (color[u]) continue;
		color[u] = 1;
		int v = to[u]; leaf[v] = true;
		if (!--in[v]) ++minn, q.push(v);
	}
	for (int i=1; i<=n; i++) cycle[i] ^= 1;
}
inline void dfs()
{
	for (int i=1; i<=n; i++)
		if (cycle[i] && !color[i])
		{
			int now = i, len = 0; bool cyc = false;
			while (true)
			{
				if (!cycle[now]) break;
				cycle[now] = false; cyc |= leaf[now];
				now = to[now]; ++len;
			}
			if ((len > 1) && !cyc) ++maxn; // Warn. len = 1
			minn += (len >> 1);
		}
}
int main()
{
	file("maf");
	scanf("%d", &n);
	for (int i=1; i<=n; i++){scanf("%d", to + i); ++in[to[i]];}
	bfs(); dfs();
	printf("%d %d\n", n - minn, n - maxn);
	return 0;
}

D. 翻转游戏

原题链接:POJ1753 Filp Game .

爆搜,状态压缩一下,每次转移的时候展开即可 .

参考代码

代码比较丑 .

typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e6 + 500;
bool vis[N];
int st, ans = -1, tmp[4][4];
inline int trans_to_num()
{
	int x = 0;
	for (int i=0; i<4; i++)
		for (int j=0; j<4; j++) x = (x << 1) + tmp[i][j];
	return x;
}
inline void trans_to_arr(int x)
{
	for (int i=3; i>=0; i--)
		for (int j=3; j>=0; j--){tmp[i][j] = x & 1; x >>= 1;} 
}
inline void bfs()
{
	queue<pii> q; q.push(make_pair(st, 0));
	while (!q.empty())
	{
		auto _ = q.front(); int x = _.first, step = _.second; q.pop();
		if (vis[x]) continue;
		vis[x] = true;
		if (!x || (x == 65535)){printf("%d\n", step); exit(0);}
		trans_to_arr(x);
		for (int i=0; i<4; i++)
			for (int j=0; j<4; j++)
			{
				tmp[i][j] ^= 1;
				if (i-1 >= 0) tmp[i-1][j] ^= 1;
				if (i+1 < 4) tmp[i+1][j] ^= 1;
				if (j-1 >= 0) tmp[i][j-1] ^= 1;
				if (j+1 < 4) tmp[i][j+1] ^= 1;
				int nx = trans_to_num();
				if (!vis[nx]) q.push(make_pair(nx, step + 1));
				tmp[i][j] ^= 1;
				if (i-1 >= 0) tmp[i-1][j] ^= 1;
				if (i+1 < 4) tmp[i+1][j] ^= 1;
				if (j-1 >= 0) tmp[i][j-1] ^= 1;
				if (j+1 < 4) tmp[i][j+1] ^= 1;
			}
	}
}
string s[4];
int main()
{
	file("flip");
	for (int i=0; i<4; i++) cin >> s[i];
	for (int i=0; i<4; i++)
		for (int j=0; j<4; j++) st = (st << 1) + (s[i][j] == 'b');
	bfs();
	puts("Impossible");
	return 0;
}
posted @ 2022-06-06 20:58  Jijidawang  阅读(144)  评论(3编辑  收藏  举报
😅​