牛客多校第六场部分题题解

E.回文自动机上dfs

  • 题目:Palindrome Mouse

  • 链接:https://ac.nowcoder.com/acm/contest/886/C

  • 大意:给你一个1e5的串,要求串的回文子串A是回文子串B的子串,求(A,B)串对的个数

  • 分析:首先是跑一段回文自动机了然后遍历回文自动机中的串,加上该串的子串个数。回文自动机的fail和ch两种指针是分别够成两棵树的。在回文树中,A是B的子串,当且仅当:串A通过ch指针和fail反向指针可以到达B。于是我们可以通过ch指针来枚举B,为了统计可以和B配对的A的数量。由于每个点的只被一个ch指针指着(不像SAM),所以我们用ch指针的时候不用去管ch指针,我们只需要把新探索的点的fail链都加入到覆盖区域,维护B点通过ch反向指针和fail指针的覆盖区域,再遍历所有点,就出来了。

  • 代码

#include
#define F(i,a,b) for(int i=(a);i<=int(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = int(1e5+9);
#define endl '\n'

const int maxm = 30;
struct PAM {
	struct nd
	{
		//长度,faile指针,最后更新该串的次数,出现次数,回文后缀个数(depth),
		int len, fail, to[maxm];
		//str可显示结点对应的字符串
		string str;
		int pos;
		int inc, cnt, dep;
		//vectorG;
	} ns[maxn];

	// s[1,size]为字符串,[0,nid]为结点空间,las为最后插入字符所对应的节点
	int size, nid, las, s[maxn];
	void init()
	{
		ans = 0;
		size = 0; nid = -1; las = 0;
		ns[++nid] = nd(); ns[nid].len = 0; ns[nid].fail = nid + 1;//0结点 偶回文
		ns[++nid] = nd(); ns[nid].len = -1; ns[nid].fail = nid - 1;//1结点 奇回文
		s[0] = '$' + 1;
	}
	int getfail(int x)  //沿fail找到第一个可插入的回文后缀
	{
		while (s[size] != s[size - ns[x].len - 1]) x = ns[x].fail;
		return x;
	}
	void push_back(int c, int pos = -1)
	{
		assert(ns[0].len == 0 && ns[1].len == -1);
		assert(c < maxm);
		s[++size] = c;
		las = getfail(las);  //找到插入的位置
		if (!ns[las].to[c])  //若没有这个节点,则新建并求出它的fail指针
		{
			ns[++nid] = nd();
			ns[nid].len = ns[las].len + 2;
			ns[nid].fail = ns[getfail(ns[las].fail)].to[c];
			ns[nid].pos = pos;
			ns[nid].dep = ns[las].dep + 1;
			//ns[ns[nid].fail].G.push_back(nid);//维护字符串拓展树
			ns[las].to[c] = nid;
			//debug用,显示每个结点对应的字符串
			//if (ns[nid].len == 1)ns[nid].str.push_back(char('a' + c));
			//else ns[nid].str = char('a' + c) + ns[las].str + char('a' + c);
			//cout << nid << ':' << ns[nid].str << '\n';
		}
		las = ns[las].to[c];
		ns[las].inc++;
	}
	void count()//重新计算每个回文串出现的次数
	{
		for (int i = nid; i >= 0; --i)ns[i].cnt = 0;
		for (int i = nid; i > 1; --i)
			ns[i].cnt += ns[i].inc, ns[ns[i].fail].cnt += ns[i].cnt;
	}
	int in[maxn << 1];
	void dfs(int u, int res)
	{
		vector<int>vec;
		for (int v = u; v > 1; v = ns[v].fail)if (!in[v])
		{
			vec.push_back(v);
			in[v] = 1;
			++res;
		}
		else break;
		ans += res;
		F(j, 0, 25)if (ns[u].to[j])
			dfs(ns[u].to[j], res);
		for (auto v : vec)in[v] = 0;
	}
	ll ans;
	void getAns()
	{
		F(i, 1, nid)in[i] = 0;
		dfs(0, 0);
		dfs(1, 0);
		ans -= nid - 1;
	}
}pam;
string A;
int main()
{
#ifndef endl
	freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	cout << "************************************Local Test*********************************" << endl;
#endif // !endl
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	int T(1), cas(0);
	cin >> T;
	while (cas, T--)
	{
		pam.init(); cin >> A;
		for (auto c : A)pam.push_back(c - 'a');
		pam.getAns();
		cout << "Case #" << ++cas << ": ";
		cout << pam.ans << '\n';

	}
	return 0;
}
//What to Debug
/*
-1.最好把全部warning都X掉,否则:https://vjudge.net/solution/19887176
0.看看自己是否有可能需要快读,禁endl
1.数组越界,爆int,浮点精度(查看精度是否达到题目要求,看有没有浮点数比较:eps),取模操作,初始化数组,边缘数据,输出格式(cas),强制在线是否更新了las
2.通读代码,代码无逻辑错误
3.读题,找到题意理解失误或算法错误
4.放弃
*/

E.暴力dfs-剪枝-读题技巧

  • 题目:Is Today Friday?

  • 链接:https://ac.nowcoder.com/acm/contest/886/D

  • 大意:这题看上去其实没啥东西。10组样例,每组给1e5个日期xxxx/xx/xx,但日期给的不是数字,给的是字母,要求你建立一个字母到数字的映射,使日期在一个范围内且合法且是星期五。

  • 分析:暴力dfs,把每种映射都尝试一遍,但是这样肯定会T, 10 ∗ ( 8 ! + 1 e 5 ∗ 判 断 一 个 日 期 需 要 的 常 数 ) 10*(8!+1e5*判断一个日期需要的常数) 10(8!+1e5)。所以这个题目,有两个小(da)技巧,第一个很容易想到,因为日期有很多是非法的,可以在dfs的分支结点就判断出来,所以可以剪枝剪掉一部分,变成了 10 ∗ ( 8 ! ∗ 0.12 ∗ 0.3 + 1 e 5 ∗ 判 断 一 个 日 期 需 要 的 常 数 10*(8!*0.12*0.3+1e5*判断一个日期需要的常数 10(8!0.120.3+1e5。这个时候判断所有日期所需的时间就成为算法的瓶颈了。这个时候要想到一点:出题人几乎不能出一个一组数据,这组数据中的每个日期都是星期5,并且日期中的字符串不重复,因为你想一想,从字符到数字是要建立映射的,而字符又只有26个,数字又只有10个,所以按照这个日期的要求建立映射了,就很难满足另外一个日期字符串的要求。所以:题目的n很大的时候,要么是可以check日期的时候快速break掉,要么是有大量重复的->去重即可。于是判断日期所需的时间就可以玄学优化掉了

#include
#include
#pragma GCC optimize(3)
#define F(i,a,b) for(int i=(a);i<=int(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = int(1e5 + 9);
#define endl '\n'

bool isrun(int y) { return y % 4 ? 0 : (y % 100 ? 1 : (y % 400 ? 0 : 1)); }
int L[10005];
int mdays[2][24] = { {-inf,31,28,31,30,31,30,31,31,30,31,30,31},{-inf,31,29,31,30,31,30,31,31,30,31,30,31} };
int Lmdays[2][24];


unordered_set<int>S;
int da[maxn][8];
int A[20];
int n, m;
void init()
{
	F(i, 1, 10000)L[i] = L[i - 1] + (isrun(i) ? 366 : 365);
	F(i, 0, 1)
	{
		F(j, 1, 12)
		{
			Lmdays[i][j] = Lmdays[i][j - 1] + mdays[i][j];
		}
	}
}
int caldays(int yyyy, int mm, int dd)
{
	int days(0);
	bool is = isrun(yyyy);
	return (L[yyyy - 1] + Lmdays[is][mm - 1] + dd) % 7;
}
inline bool isFri(int y, int m, int d)
{
	if (y < 1600)return 0;
	if (m < 1 || m>12)return 0;
	if (d<1||d > mdays[isrun(y)][m])return 0;

	return caldays(y, m, d) == 5;
}
int r[maxn];
int nck(0);
inline bool check()
{
	++nck;
	//F(iid, 1, m)
	F(i,1,m)
	{
		//int i = r[iid];
		int y(0);
		F(j, 0, 3)y *= 10, y += A[da[i][j]];
		int m(0);
		F(j, 4, 5)m *= 10, m += A[da[i][j]];
		int d(0);
		F(j, 6, 7)d *= 10, d += A[da[i][j]];
		if (!isFri(y, m, d))
			return 0;
	}
	return 1;
}

bool in[20];
bool show5[12], show4[12], show6[12];
bool dfs(int pos)
{
	//if (nck >= 2e6)return 0;
	if (pos == 10)
		return check();
	F(i, 0, 9)
	{
		if (!in[i])
		{
			in[i] = true;
			A[pos] = i;
			if (dfs(pos + 1))return 1;
			else
			{
				in[i] = false;
			}
		}
	}
	return false;
}
int main()
{
#ifndef endl
	freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	cout << "************************************Local Test*********************************" << endl;
#endif // !endl
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	
	srand(int(time(0)));
	int T(1), cas(0);
	init();
	cin >> T;
	while (cas, T--)
	{
		
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
		memset(in, 0, sizeof(in));
		memset(show4, 0, sizeof(show4));
		memset(show5, 0, sizeof(show5));
		memset(show6, 0, sizeof(show6));
		S.clear();
		m = 0;
		cin >> n;
		F(i, 1, n)
		{
			char c;
			++m;
			F(j, 0, 3)cin >> c, da[m][j] = c - 'A';
			cin >> c;
			F(j, 4, 5)cin >> c, da[m][j] = c - 'A';
			cin >> c;
			F(j, 6, 7)cin >> c, da[m][j] = c - 'A';
			int val(0);
			F(j, 0, 7)val *= 10, val += da[m][j];
			if (S.find(val) != S.end())--m;
			else S.insert(val);
			show4[da[m][4]] = 1;
			show6[da[m][6]] = 1;
		}

		dfs(0);
		cout << "Case #" << ++cas << ": ";
		if (check())
		{
			F(i, 1, 10)
			{
				cout << A[i - 1];
			}
			cout << '\n';
		}
		else cout << "Impossible" << '\n';
		//cout << nck << endl;
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
	}
	return 0;
}
//What to Debug
/*
-1.最好把全部warning都X掉,否则:https://vjudge.net/solution/19887176
0.看看自己是否有可能需要快读,禁endl
1.数组越界,爆int,浮点精度(查看精度是否达到题目要求,看有没有浮点数比较:eps),取模操作,初始化数组,边缘数据,输出格式(cas),强制在线是否更新了las
2.通读代码,代码无逻辑错误
3.读题,找到题意理解失误或算法错误
4.放弃
*/

E.图论-最短路-最短路和

  • 题目:Train Driver

  • 链接:https://ac.nowcoder.com/acm/contest/886/H

  • 大意:给出一个n,m最大1e5的图,给你2个maxsize都是20的点集A,和B。从A集合,B集合,图中分别任意选3个点a,b,c,再求 ∑ i ∈ v , a ∈ A , b ∈ B , c ∈ V m i n ( d i s ( a , i ) + d i s ( b , i ) + d i s ( c , i ) ) \sum_{i∈v,a∈A,b∈B,c∈V} min{(dis(a,i)+dis(b,i)+dis(c,i))} iv,aA,bB,cVmin(dis(a,i)+dis(b,i)+dis(c,i))

  • 分析:(a,b)的情况有400种,可以暴力遍历,接下来还有求对c选所有点时候的答案。当 c ′ c' c=c时,dis=dis(a,c)+dis(b,c),当dis( c ′ c' c,c)=1时, d i s = m i n ( d i s ( a , c ′ ) + d i s ( b , c ′ ) , d i s ( a , c ) + d i s ( b , c ) + d i s ( c , c ′ ) dis=min(dis(a,c')+dis(b,c'),dis(a,c)+dis(b,c)+dis(c,c') dis=min(dis(a,c)+dis(b,c),dis(a,c)+dis(b,c)+dis(c,c)是不是有点像最短路的松弛操作,你说对了,可以将每个点的dis初始化为dis(a,i)+dis(b,i)再通过边来进行松弛。再加上所有边权都为1,所以还可以优化最短路算法,这个可以看代码

#include
#define F(i,a,b) for(int i=(a);i<=int(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 9;
int disa[21][maxn], disb[21][maxn], dis[maxn];
int n, m;
int a[21], b[21], an, bn;
vector<int>G[maxn];
vector<int>H[maxn * 2];
void initdis(int i, int use[], int len, int d[])
{
	//memset(d, inf, sizeof(int)*(len+3));
	static int vis[maxn];
	F(i, 1, n)vis[i] = 0;
	int u = use[i];
	queue<int>Q; Q.push(u); Q.push(0); vis[u] = 1; d[u] = 0;
	while (Q.size())
	{
		int x = Q.front(); Q.pop();
		int val = Q.front(); Q.pop();
		for (auto&to : G[x])if (!vis[to])
		{
			vis[to] = 1;
			d[to] = val + 1;
			Q.push(to);
			Q.push(val + 1);
		}
	}
}
ll gcd(ll a, ll b) { if (a < b)swap(a, b); return b == 0 ? a : gcd(b, a%b); }

ll solve(int x, int y)
{
	F(i, 1, n)dis[i] = disa[x][i] + disb[y][i], H[dis[i]].push_back(i);
	F(i, 0, n * 2)
	{
		for (auto &u : H[i])if (dis[u] == i)
		{
			for (auto &v : G[u])if (dis[v] > dis[u] + 1)
			{
				dis[v] = dis[u] + 1;
				H[dis[v]].push_back(v);
			}
		}
		H[i].clear();
	}
	ll sum = accumulate(dis + 1, dis + 1 + n, 0ll);
	return sum;
}
#define endl '\n'
int main()
{
#ifndef endl
	freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	cout << "************************************Local Test*********************************" << endl;
#endif // !endl
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	int T(1), cas(0);
	cin >> T;
	while (cas, T--)
	{
		cin >> n >> m;
		F(i, 1, m) { int f, t; cin >> f >> t; G[f].push_back(t); G[t].push_back(f); }
		cin >> an;
		F(i, 1, an)cin >> a[i], initdis(i, a, an, disa[i]);
		cin >> bn;
		F(i, 1, bn)cin >> b[i], initdis(i, b, bn, disb[i]);
		ll cnt = 1ll * an*bn*n;
		ll sum(0);
		F(i, 1, an)F(j, 1, bn)
		{
			sum += solve(i, j);
		}

		ll g = gcd(sum, cnt);
		sum /= g; cnt /= g;
		cout << "Case #" << ++cas << ": ";
		cout << sum;
		if (cnt != 1)cout << "/" << cnt;
		cout << endl;

		F(i, 1, n)G[i].clear();
	}
	return 0;
}
//What to Debug
/*
-1.最好把全部warning都X掉,否则:https://vjudge.net/solution/19887176
0.看看自己是否有可能需要快读,禁endl
1.数组越界,爆int,浮点精度(查看精度是否达到题目要求,看有没有浮点数比较:eps),取模操作,初始化数组,边缘数据,输出格式(cas),强制在线是否更新了las
2.通读代码,代码无逻辑错误
3.读题,找到题意理解失误或算法错误
4.放弃
*/

你可能感兴趣的:(学习笔记,ACM)