CF Diary V

11.22-4.29
10 题一篇 >o<

E. Count Seconds

*2200

题意

一个 n 个节点 m(1n,m10000) 条边的 DAG ,仅有一个点没有出度,每个点有值 ai(0ai109) ,每秒钟值 0 的点的权值会 1 ,其所指向的点的权值都会 +1 。求所有点的权值都为 0 时的最短时间, mod 998244353

题解

考虑拓扑排序,如果一个点一直向后流动,那么其清空的时间就是它所有前驱权值总和,但可能有的点不会一直流动,考虑直接模拟前 n 秒,由于最长链不超过 n ,所以所有点最后接收到的流在这之前一定抵达,也就是在 n 秒后所有点在彻底清空之前一定会不停向后流动,此时在按之前的做法做就可以了,复杂度 O(nm)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 100010;

LL T, N, M, A[1010], in[1010];
bool po[1010];
vector<int>G[1010];

void add_edge(int from, int to)
{
	G[from].push_back(to);
}

void solve()
{
	int F = 0;
	for (int i = 1; i <= N; i++)
	{
		if (!G[i].size())
		{
			F = i;
			break;
		}
	}
	for (int i = 1; i <= N + 5; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			if (A[j])
				break;
			if (j == N)
			{
				cout << i - 1 << endl;
				return;
			}
		}
		for (int v = 1; v <= N; v++)
		{
			if (A[v])
				po[v] = true;
			else
				po[v] = false;
		}
		for (int v = 1; v <= N; v++)
		{
			if (po[v])
			{
				for (auto& to : G[v])
					A[to]++;
				A[v]--;
			}
		}
	}
	queue<int>que;
	for (int i = 1; i <= N; i++)
	{
		if (!in[i])
			que.push(i);
	}
	while (!que.empty())
	{
		int v = que.front(); que.pop();
		for (auto& to : G[v])
		{
			in[to]--;
			if (!in[to])
				que.push(to);
			A[to] = (A[v] + A[to]) % mod;
		}
	}
	cout << (A[F] + N + 5) % mod << endl;
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		cin >> N >> M;
		for (int i = 1; i <= N; i++)
			cin >> A[i], G[i].clear(), in[i] = 0;
		int x, y;
		for (int i = 1; i <= M; i++)
			cin >> x >> y, add_edge(x, y), in[y]++;
		solve();
	}
	return 0;
}

1761D. Carry Bit

*2100

题意

f(x,y)x+y 的二进制进位数,给定两个整数 nk(0k<n106) ,求出满足 0a,b<2nf(a,b)=k有序(a,b) 的数量。对 109+7 取模。

题解

考虑 (a,b) 每位上的一对数字,容易发现一个连续的进位区间必定由 (1,1) 开始, 在第一个 (0,0) 或者尽头处终结,进位数位段长 1 (在尽头处结束不减一)。于是考虑枚举段数 i ,这些段总长为 k+i ,各段段长 >1 。于是考虑先只考虑放 nk 个数,在这些数中选 i 个作为段头,之后再找 k 个数,插板分成 i>0 的段放入各段首后即可,即 (nki)(k1i1) 。剩下的部分,对于非段内的位,除了 (1,1) 都可以,对于段内,除了 (0,0) 都可以,于是再乘上 3n2i 。对于最开头的没有段头的段的情况,没有被算入,于是再计算这部分,即段头少选一个即可,可选 3 个的总长改为 n2i+1 即可。最后的答案为:(nki)(k1i1)3n2i+(nki1)(k1i1)3n2i+1 。对于 k=0 ,答案为 3n ,特判一下即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;

LL N, K, f[maxn], invf[maxn];

LL qpow(LL a, LL x)
{
	if (x < 0)
		return 0;
	LL ans = 1;
	while (x)
	{
		if (x & 1)
			ans = ans * a % MOD;
		a = a * a % MOD, x >>= 1;
	}
	return ans;
}

LL C(LL x, LL y)
{
	if (x < 0 || y < 0 || x < y)
		return 0;
	return f[x] * invf[y] % MOD * invf[x - y] % MOD;
}

void solve()
{	
	f[0] = 1;
	for (LL i = 1; i <= N; i++)
		f[i] = f[i - 1] * i % MOD;
	invf[N] = qpow(f[N], MOD - 2);
	for (LL i = N; i > 0; i--)
		invf[i - 1] = invf[i] * i % MOD;
	if (K == 0)
	{
		cout << qpow(3ll, N) << endl;
		return;
	}
	LL ans = 0;
	for (LL i = 0; i <= K; i++)
	{
		LL tmp = qpow(3ll, K - i) * C(K - 1, i - 1) % MOD * ((C(N - K, i) * qpow(3ll, N - K - i) % MOD + C(N - K, i - 1) * qpow(3ll, N - K - i + 1) % MOD) % MOD) % MOD;
		ans = (ans + tmp) % MOD;
	}
	cout << ans << endl;
}

int main()
{
	IOS;
	cin >> N >> K;
	solve();
	return 0;
}

1748D. ConstructOR

*2100

题意

找到一个整数 x(0x<260) ,使得 d|(a or x)d|(b or x)1a,b,d<230)

题解

考虑直接让最后 a or x=b or x 。于是我们先让二者或上 c=a or b 。考虑如果 lowbit(c)<lowbit(d) 那么必然无解,现在我们至多只使用了后 30 为,而还有前面 30 位来进行接下来的操作,之后考虑记 z230+c=kd ,即 kdz230=c ,此方程无解当且仅当 gcd(230,d)c ,显然 gcd(230,d)=lowbit(d) ,而我们在这里只考虑 lowbit(c)lowbit(d) 的情况,显然 gcd(230,d)c 此时一定会有解,于是直接扩欧求解,让解尽可能小即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair < int, int > PII;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1000000007;
const int maxn = 1000010;

LL T, A, B, D;

LL lowbit(LL x)
{
	return x & (-x);
}

LL extgcd(LL a, LL b, LL& x, LL& y)
{
	if (b == 0)
	{
		x = 1, y = 0;
		return a;
	}
	LL d = extgcd(b, a % b, x, y),  z = x;
	x = y, y = z - y * (a / b);
	return d;
}

void solve()
{
	LL C = A | B;
	if (lowbit(C) < lowbit(D))
	{
		cout << -1 << endl;
		return;
	}
	A |= C, B |= C;
	LL x, y, K = C / lowbit(D), M = 1ll << 30, G = D / lowbit(D);
	extgcd(D, 1ll << 30, x, y);
	LL ans = -y * K;
	if (ans > 0)
		ans -= ans / G * G;
	else
		ans += ((-ans / G) + 1) * G;
	cout << (ans << 30) + C << endl;
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
		cin >> A >> B >> D, solve();

	return 0;
}

1599H. Hidden Fortress

*2100

题意

交互题,在一个 109×109 的网格内开始有一个矩形,保证矩形的任何一条边不在边界上。每次询问 (x,y)(x,y) 不能在矩形内,得到 (x,y) 到该矩形上最近一个点的曼哈顿距离, 在 40 次询问内,找出这个矩形。

题解

考虑询问出每一条边到边界的距离,比如将询问固定在第一行,询问的答案一定是先减小,再保持不变,再增大,保持不变那部分就是矩形上边那个边距离上边界的距离,考虑先询问左上角和右上角,之后根据二者的答案移动其中一个点的横坐标使得二者答案一致(因为增大,减小段的斜率分别为 11 ,所以很好办)然后二者横坐标的中点一定落在不变的区间内,再对这个横坐标询问一下即可,对其他边也一样处理,最后询问区域的四个角,再对每条边询问一次答案,总共 8 次询问就可以了。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
//#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 200010;

LL ask(int x, int y)
{
	LL ret = 0;
	cout << "? " << x << ' ' << y << endl;
	cin >> ret;
	return ret;
}

void solve()
{
	LL ans0 = ask(1, 1), ans1 = ask(1, 1e9), ans2 = ask(1e9, 1), ans3 = ask(1e9, 1e9);
	LL x1 = ask(1, (1ll + ans0 - ans1 + 1e9) / 2);
	LL x2 = ask((1ll + ans0 - ans2 + 1e9) / 2, 1);
	LL x3 = ask(1e9, (1ll + ans2 - ans3 + 1e9) / 2);
	LL x4 = ask((1ll + ans1 - ans3 + 1e9) / 2, 1e9);
	cout << "! " << x1 + 1 << ' ' << x2 + 1 << ' ' << (LL)1e9 - x3 << ' ' << (LL)1e9 - x4 << endl;
}

int main()
{
	IOS, solve();
	return 0;
}

1762D. GCD Queries

*2100

题意

交互题,一个 0n1(2n104) 的排列,每次询问 gcd(ai,aj) ,求两个下下标 i,j ,满足至少有一个其位置上的元素是零。

题解

考虑 n=2 时答案为 1,2 ,对于更大的 n ,逐个考虑下标 3n 是否更新为答案,每次对于下标 i ,当前答案为 x y ,询问 (x,i),(y,i) ,加入 i 后有 3 个下标,要选一个不可能为 0 的扔掉,分情况讨论,如果 gcd(ax,ai)=gcd(ay,ai) ,则 ai0 ,否则 ax=ay ,与排列矛盾。之后如果 gcd(ax,ai)<gcd(ay,ai) ,如果 ax=0 ,则 ai<gcd(ay,ai) ,这显然是矛盾的,于是 ax0 ,同理,剩下的一种情况 ay0 ,直接做即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair < LL, LL > PII;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
//#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1000000007;
const int maxn = 100010;

int T, N;

int ask(int i,int j)
{
	int ret;
	cout << "? " << i << " " << j << endl;
	cin >> ret;
	return ret;
}

void solve()
{
	int x = 1, y = 2;
	for (int i = 3; i <= N; i++)
	{
		int tx = ask(x, i), ty = ask(y, i);
		if (tx == ty)
			continue;
		else if (tx < ty)
			x = i;
		else
			y = i;
	}
	cout << "! " << x << " " << y << endl, cin >> N;
}

int main()
{
	IOS, cin >> T;
	while (T--)
		cin >> N, solve();
	return 0;
}

1779E. Anya's Simultaneous Exhibition

Difficulty:2400

题意

交互题,有 n(2n200) 个人,两两之间对战的结果确定,每次询问一个人和他的若干个对手,回答这个人能击败其中多少对手。要进行 n1 场对战, 2n 次询问内求出有哪些人可能是最后的胜者。

题解

对于 u,v 如果 u 能够战胜 v ,则连一条 uv 的有向边。这样连出来的图是一张竞赛图(任意两节点间有且仅有一条有向边)。对该图缩点后要么是一条链,要么是链上每个点仅有向后的连边的这样一种形式,其中链首节点所包含的节点即为所求。每次询问如果询问其余所有人作为对手的情况,实际上相当于询问该节点的出入度,先进行 n 次询问,之后按出度从大到小排序,由于竞赛图的性质,某个 scc 内的点会向所有次序靠后的 scc 中的所有点有一条连边,首个 scc 内的点出度一定是比后面的都要大,记每个 scc 节点数为 ai ,那么其至少为 1+i=2mai ,而后面的节点至多为 i=jmai1 。并且对排序好的各节点入度做前缀和,第一个满足入度等于 (i2)i ,前 i 个节点即为首个 scc 。因为第一个 scc 的入度全部来自于自己内部连边的贡献,于是仅需 n 次询问即可求出答案。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair < LL, LL > PII;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define int LL
#define lc p*2+1
#define rc p*2+2
//#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1000000007;
const int maxn = 260;

int N, in[maxn];
PII A[maxn] ;

int ask(int v,string s)
{
	int ret;
	cout << "? " << v << ' ' << s << endl;
	cin >> ret;
	return ret;
}

void solve()
{
	int cnt = 0;
	string S(N, '1'), ans(N, '0');
	S[0] = '0', A[1].first = ask(1, S), A[1].second = 1, in[1] = N - 1 - A[1].first;
	for (int i = 2; i <= N; i++)
		S[i - 1] = '0', S[i - 2] = '1', A[i].first = ask(i, S), A[i].second = i, in[i] = N - 1 - A[i].first;
	sort(A + 1, A + N + 1), reverse(A + 1, A + N + 1);
	for (int i = 1; i <= N; i++)
	{
		cnt += in[A[i].second], ans[A[i].second - 1] = '1';
		if (cnt == i * (i - 1) / 2)
			break;
	}
	cout << "! " << ans << endl;
}

int main()
{
	IOS, cin >> N, solve();
	return 0;
}

1806D. DSU Master

Difficulty:2500

题意

n 个节点,长为 n1 的数组 aa1{0,1} ,对于一个长为 n1 的排列 p ,从 i=1n1 执行操作,找到 pipi+1 分别所在的弱连通块的唯一的仅有入边的节点 uv 。如果 api=0 ,则从 vu 连边,否则 uv 连边。求对于每个 k[1,n1] ,所有长为 k 的排列建出的图中节点 1 的入度之和。

题解

考虑每个节点的贡献,当 ai=0 时,i+1 所在块会向其所在块连边,若要产生贡献,必须满足 i1 在一个块中且 1 没有出边。
发现每次操作其实类似于并查集合并,每个集合内的数全部连续,每次合并后也是将两个相邻的连续区间合并,因此上述要满足的第一个条件也就变为了 1i1 在排列中全部位于 i 之前。
ai=1 时,此时无论如何不会产生贡献,因为 i+11 必然不在一个集合内,同时如果此时 i1 在一个集合内,则排列中在 i 之和的位置不会再产生任何贡献,即此后的 i 都不会满足 1 没有出边这个限制。
考虑 dp 来解决:
ansi 为仅考虑操作 1i 时的答案。
fi 为当 ai+1=0 时满足第二个条件的所有排列。
由于当新加入 i 后,无论插入到先前任意排列的任意位置,都不会对已有的贡献产生影响(ai=0 插进去不满足第一个条件, ai=1 插进去也同样不满足该条件)这部分为 ansi1i,同理也不会对 f 产生影响, fi=fi1i ,如果插在尾部,则 ai=0 时满足第一个条件,于是对于所有满足第二个条件的序列都会额外产生 1 的贡献,需要加上 fi1 ,同时序列继续满足。否则,同样不会产生贡献,但是对于 f ,此时出发了使后面贡献为 0 的条件,于是 fi=fi1(i1)
最后的式子为:

fi=fi1(iai)ansi=ansi1i+(1ai)fi1

初值 f1=1ans1=1 ,特判一下 a1=1 时全 0 的情况。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 500010;

LL T, N, A[maxn], ans[maxn], f[maxn];

void solve()
{
	if (A[1] == 1)
	{
		for (int i = 1; i < N; i++)
			cout << 0 << ' ';
		cout << endl;
		return;
	}
	f[1] = 1, ans[1] = 1;
	for (LL i = 2; i < N; i++)
		f[i] = f[i - 1] * (i - A[i]) % mod, ans[i] = (ans[i - 1] * i % mod + (1 - A[i]) * f[i - 1]) % mod;
	for (int i = 1; i < N; i++)
		cout << ans[i] << ' ';
	cout << endl;
}

int main()
{
	IOS, cin >> T;
	while (T--)
	{
		cin >> N;
		for (int i = 1; i < N; i++)
			cin >> A[i];
		solve();
	}
	return 0;
}

1801C. Music Festival

题意

n 个长为 ki(ki2105) 的序列,现在要重新排布这些序列,定义重新排布后序列的值为序列中能作为某个前缀的严格最大值的位置的数量。求最大可能的值。

题解

对于每个序列,如果某个位置前面有大于等于它的数,那这个位置对答案必然是无贡献的,于是我们可以先预处理以下所有序列,删去这些数,得到的每个序列都是上升的。之后考虑将这些序列拼接起来,对与每个序列的最大值,如果前面有序列的最大值大于等于它,那么也没有贡献了,相当于在连接的过程中没有选这些序列,将所有序列按最大值排序,于是问题转化为了在这些序列中选若干个,他们的贡献最大是多少。
考虑记 fi 为最后一个选了第 i 个序列是的答案,显然转移为 max1j<i{fj+kx+1} ,其中第 i 个序列的第 x 个数是序列中第一个大于第 j 个序列最大值的,转移的时候可以枚举 x ,相当于求末尾序列最大值在 [1,x1] 中的状态的最大值,可以用线段树来维护,由于每次建树的值域比较大,所以用动态开点的写法,最后答案为 maxfi ,复杂度 O(nlogn)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 500010;

int T, N, f[maxn], tot = 0;
vector<int>G[maxn];
PII P[maxn];
struct node {
	int lc, rc, dat;
}tr[maxn * 4];

int build()
{
	++tot;
	tr[tot].lc = tr[tot].rc = tr[tot].dat = 0;
	return tot;
}

void pushup(int p)
{
	tr[p].dat = max(tr[tr[p].lc].dat, tr[tr[p].rc].dat);
}

void modify(int p, int l, int r, int a, int v)
{
	if (l + 1 == r)
	{
		tr[p].dat = max(tr[p].dat, v);
		return;
	}
	int mid = (l + r) / 2;
	if (a < mid)
	{
		if (!tr[p].lc)
			tr[p].lc = build();
		modify(tr[p].lc, l, mid, a, v);
	}
	else
	{
		if (!tr[p].rc)
			tr[p].rc = build();
		modify(tr[p].rc, mid, r, a, v);
	}
	pushup(p);
}

int query(int p, int l, int r, int a, int b)
{
	if (p == 0)
		return 0;
	if (a <= l && b >= r)
		return tr[p].dat;
	int mid = (l + r) / 2;
	if (b < mid)
		return query(tr[p].lc, l, mid, a, b);
	else if (a >= mid)
		return query(tr[p].rc, mid, r, a, b);
	else
		return max(query(tr[p].lc, l, mid, a, mid), query(tr[p].rc, mid, r, mid, b));
}

void solve()
{
	tot = 0, sort(P + 1, P + N + 1), build();
	int ans = 0;
	for (int i = 1; i <= N; i++)
	{
		int p = P[i].second, k = G[p].size();
		for (int j = 0; j < k; j++)
			f[i] = max(f[i], query(1, 1, 200001, 1, G[p][j]) + k - j);
		modify(1, 1, 200001, G[p][k - 1], f[i]);
	}
	for (int i = 1; i <= N; i++)
		ans = max(ans, f[i]);
	cout << ans << endl;
}

int main()
{
	IOS, cin >> T;
	while (T--)
	{
		int k, mx, tmp;
		cin >> N;
		for (int i = 1; i <= N; i++)
			G[i].clear(), f[i] = 0;
		for (int i = 1; i <= N; i++)
		{
			cin >> k, mx = 0;
			for (int j = 1; j <= k; j++)
			{
				cin >> tmp;
				if (tmp > mx)
					G[i].push_back(tmp), mx = tmp;
			}
			P[i] = PII(mx, i);
		}
		solve();
	}
	return 0;
}

11D. A Simple Task(*2200)

题意

求无向图简单环数量, 1n191m ,无重边自环。

题解

考虑状压 dp ,记 fi,j 为由点集 i 组成的,起点为 lowbit(i) ,终点为 j 的不同简单路径条数,如果起终点一致,则计入答案贡献,枚举所有状态对当前终点的所有边转移,如果目标节点已经走过,则考虑是否计入贡献,否则不是简单路径,不进行转移;如果没走过,直接转移即可,注意新的终点不能更新 lowbit(i) 因为这样起点就变了,否则不转移,复杂度 O(2nn2)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;

LL N, M, f[550010][20];
bool G[20][20];

void solve()
{
	LL ans = 0;
	for (int i = 1; i <= N; i++)
		f[1 << i - 1][i] = 1;
	for (int i = 1; i <= (1 << N); i++)
	{
		for (int j = 1; j <= N; j++)
		{
			if (!f[i][j])
				continue;
			for (int k = 1; k <= N; k++)
			{
				if (!G[j][k])
					continue;
				if ((i & (-i)) > (1 << k - 1))
					continue;
				if ((1 << k - 1) == (i & (-i)))
				{
					ans += f[i][j];
					continue;
				}
				if ((i >> k - 1) & 1)
					continue;
				f[i | (1 << k - 1)][k] += f[i][j];
			}
		}
	}
	cout << (ans - M) / 2 << endl;
}

int main()
{
	IOS, cin >> N >> M;
	int u, v;
	for (int i = 1; i <= M; i++)
		cin >> u >> v, G[u][v] = G[v][u] = true;
	solve();
	return 0;
}

1056E. Check Transcription(*2100)

题意

一个 01s(2|s|106) ,保证 0,1 都存在;一个小写字母串 t(1|t|105) 。求有多少对不同的小写字母串 t0,t1(t0t1) ,将 s 中所有 0 换成 t01 换成 t1 后,得到 t

题解

考虑枚举 s 中第一种出现字符对应的串的长度 i ,此时另一种对应的长度也确定,之后可以考虑遍历 s ,比较之后各串与一开始枚举/确定的串是否相等,这一步通过计算后缀间最长公共前缀长度与一开始的串长即可,通过后缀数组与 ST 表实现单次查询 O(1) ,由于每种长度只会枚举一次,比较的总次数不超过 i=1n|t|i ,即 O(|t|log|t|) ,构造后缀数组 O(|s|log2|s|) 。注意特判 t0=t1 的情况。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;
string s, t;
int N, M, K, rk[maxn], tmp[maxn], sa[maxn], lcp[maxn], ST[maxn][20];

bool compare_sa(int i, int j)
{
	if (rk[i] != rk[j])
		return rk[i] < rk[j];
	else
	{
		int ri = i + K <= N ? rk[i + K] : -1;
		int rj = j + K <= N ? rk[j + K] : -1;
		return ri < rj;
	}
}

void construct_sa()
{
	for (int i = 0; i <= N; i++)
	{
		sa[i] = i;
		rk[i] = i < N ? t[i] : -1;
	}
	for (K = 1; K <= N; K *= 2)
	{
		sort(sa, sa + N + 1, compare_sa);
		tmp[sa[0]] = 0;
		for (int i = 1; i <= N; i++)
			tmp[sa[i]] = tmp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);
		for (int i = 0; i <= N; i++)
			rk[i] = tmp[i];
	}
}

void construct_lcp()
{
	for (int i = 0; i <= N; i++)
		rk[sa[i]] = i;
	int h = 0;
	lcp[0] = 0;
	for (int i = 0; i < N; i++)
	{
		int j = sa[rk[i] - 1];
		if (h > 0)
			h--;
		for (; j + h < N && i + h < N; h++)
		{
			if (t[j + h] != t[i + h])
				break;
		}
		lcp[rk[i] - 1] = h;
	}
}

void LCP_init(int n)
{
	for (int i = 0; i < n; i++)
		ST[i][0] = lcp[i];
	for (int j = 1; (1 << j) <= n; j++)
	{
		for (int i = 0; i + (1 << j) - 1 < n; i++)
			ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
	}
}

int LCP(int l, int r)//[l,r)
{
	if (l >= r)
		return 0;
	int k = floor(log2(r - l));
	return min(ST[l][k], ST[r - (1 << k)][k]);
}

void solve()
{
	N = t.length(), M = s.length(), construct_sa(), construct_lcp(), LCP_init(N + 1);
	int x = 0, y = 0, ans = 0, f0 = inf, f1 = inf;
	for (int i = 0; i < M; i++)
	{
		if (s[i] == '0')
			x++, f0 = min(f0, i);
		else
			y++, f1 = min(f1, i);
	}
	if (s[0] == '1')
		swap(x, y);
	for (int i = 1; i <= N - 1; i++)
	{
		int a = i, b = N - x * i;
		if (b <= 0)
			break;
		if (b % y)
			continue;
		b /= y;
		int fst;
		if (s[0] == '1')
			fst = f0 * a;
		else
			fst = f1 * a;
		int now = a;
		bool flag = true;
		if (a == b && LCP(min(rk[0], rk[fst]), max(rk[0], rk[fst])) >= a)
			continue;
		for (int j = 1; j < M; j++)
		{
			if (now == fst)
			{
				now += b;
				continue;
			}
			if (s[j] == s[0])
			{
				int l = min(rk[now], rk[0]), r = max(rk[now], rk[0]), k = LCP(l, r);
				if (k < a)
				{
					flag = false;
					break;
				}
				now += a;
			}
			else
			{
				int l = min(rk[now], rk[fst]), r = max(rk[now], rk[fst]), k = LCP(l, r);
				if (k < b)
				{
					flag = false;
					break;
				}
				now += b;
			}
		}
		if (flag)
			ans++;
	}
	cout << ans << endl;
}

int main()
{
	IOS, cin >> s >> t, solve();
	return 0;
}

本文作者:Prgl

本文链接:https://www.cnblogs.com/Prgl/p/16915876.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Prgl  阅读(48)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开