题目: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.放弃
*/
题目: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.12∗0.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.放弃
*/
题目: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))} ∑i∈v,a∈A,b∈B,c∈Vmin(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.放弃
*/