补题进度: 8/13
A. Adrien and Austin
题意:博弈。n个石子,每次可以选连续的 1-k 个。问谁赢。
思路:签到题。但我没签上来。还是开局两小时后队友签上的。
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9+7;
const int MX = 1e6+7;
int mon[505];
int main()
{
int n, k;
cin>>n>>k;
if(n == 0 || (k == 1 && n%2 == 0)) puts("Austin");
else puts("Adrien");
return 0;
}
D. Country Meow
题意:题意极简。n个三维的点。 找到空间中的一个点,使得到这n个点的最大欧氏距离最小。求最小距离。
思路:看了一眼感觉是二分套二分套二分。队友纠正了一下,应该是三分。没错确实是三分。分别二分枚举三维的坐标。距离变化是一个凹函数。所以三分找凹点就好了。三维坐标那就套三层三分。每一层枚举一个坐标。然后O(n) check 一下。数据小。时间是可以接受的。不过三分还是写的不熟。赛后知道这是一个最小球覆盖模板题。模拟退火也可以解决。且更快更高级。
AC代码1(三分):
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9+7;
struct node{
double x,y,z;
}p[1005];
int n;
double dis(double xx,double yy,double zz,int i){
double x = xx-p[i].x;
double y = yy-p[i].y;
double z = zz-p[i].z;
return sqrt(x*x+y*y+z*z);
}
double check(double xx,double yy,double zz){
double res = 0;
for(int i = 0 ;i < n ; i ++){
res = max(dis(xx,yy,zz,i),res);
}
return res;
}
double s3(double xx,double yy){
double l = -1e5+7;
double r = 1e5+7;
const double EPS = 1e-9;
double res = 1e18;
while(r - l > EPS) {
double lmid = l + (r - l) / 3;
double rmid = r - (r - l) / 3;
double lans = check(lmid,xx,yy),rans = check(rmid,xx,yy);
// 求凹函数的极小值
res = min(lans,rans);
if(lans <= rans) r = rmid;
else l = lmid;
}
return res;
}
double s2(double xx){
double l = -1e5+7;
double r = 1e5+7;
const double EPS = 1e-9;
double res = 1e18;
while(r - l > EPS) {
double lmid = l + (r - l) / 3;
double rmid = r - (r - l) / 3;
double lans = s3(lmid,xx),rans = s3(rmid,xx);
// 求凹函数的极小值
res = min(lans,rans);
if(lans <= rans) r = rmid;
else l = lmid;
}
return res;
}
double s1(){
double l = -1e5+7;
double r = 1e5+7;
const double EPS = 1e-9;
double res = 1e18;
while(r - l > EPS) {
double lmid = l + (r - l) / 3;
double rmid = r - (r - l) / 3;
double lans = s2(lmid),rans = s2(rmid);
// 求凹函数的极小值
res = min(lans,rans);
if(lans <= rans) r = rmid;
else l = lmid;
}
return res;
}
signed main(){
cin>>n;
for(int i = 0 ; i < n ; i ++)
cin>>p[i].x>>p[i].y>>p[i].z;
printf("%.15f",s1());
return 0;
}
AC代码2(模拟退火):
//#include <bits/stdc++.h>
#include <math.h>
#include <cstdio>
#include <iostream>
#include <cstring>
#define int long long
using namespace std;
const int mod = 1e9+7;
struct node{
double x,y,z;
}p[1005];
int n;
double dis(double xx,double yy,double zz,int i){
double x = xx-p[i].x;
double y = yy-p[i].y;
double z = zz-p[i].z;
return sqrt(x*x+y*y+z*z);
}
double sa(){
double EPS = 1e-9;
double T = 100000;
double delta = 0.98;
double res = 1e18;
double x = 0,y = 0,z = 0;
while(T > EPS){
double diss = 0;
int d = 0;
for(int i = 0 ; i < n ; i ++){
double tmp = dis(x,y,z,i);
if(tmp > diss){
diss = tmp;
d = i;
}
}
res = min(res,diss);
x += (p[d].x - x)/diss*delta*T;
y += (p[d].y - y)/diss*delta*T;
z += (p[d].z - z)/diss*delta*T;
T *= delta;
}
return res;
}
signed main(){
while(~scanf("%lld",&n) && n){
for(int i = 0 ; i < n ; i ++)
scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].z);
printf("%.15f\n",sa());
}
return 0;
}
E. Eva and Euro coins
题意:给定一排硬币。也就是一个01串。要变成目标01串。限制是每次只能翻动连续的k个硬币。
思路:这场榜是不是被带偏了。E题应该是前期题呀。简单思维+栈。观察一下就可以发现,对于连续的k个相同的硬币。如果存在的话。那目标串里面也必然存在连续相同的才行。 其次,更重要的是,这连续的k个相同的,可以直接删掉!为什么呢。考虑这样一个串 0101000101,k = 3,这时候只有中间有连续的3对吧。把他变成1,0101111101,然后左边3个1变成0,0100001101,发现什么。左边的那个1,移动了三个0的右边。 还可以接着移动。0111101101,0000101101,0000101101,三个0,移动到了整个串的最前面。其实不只是最前面。它可以移动到任意一个位置。而其他的字符相对位置不会改变。所以这3个零直接删掉。是不影响最后结果的。因为如果 目标串也有3个0或者1。那么也把他们删掉就行了。除了可以移动连续的值外。其他的值。根本不会变。也就是说。只要把这些东西删完。剩下的一样就是一样,不一样就是不一样,直接判就好了。 因为移动会使得原本不相连的串变得相连。那就涉及到栈了。这就和连连看一样了。不断的消去就行了。判断最后剩下的串就行。
AC代码:
#include <iostream>
#include <bits/stdc++.h>
#include <unordered_map>
#define int long long
#define mk make_pair
#define gcd __gcd
using namespace std;
const double eps = 1e-10;
const int mod = 998244353;
const int N = 5e6+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N],c[N];
int mark[N];
string s1,s2;
string ss1,ss2;
stack<pair<char,int> > st;
string get_tag(string s){
for(int i = 0 ; i < n ; i ++){
if(st.empty()){
st.push({s[i],1});
}else{
pair<char,int> tmp = st.top();
if(tmp.first == s[i]){
st.top().second ++;
}else{
st.push({s[i],1});
}
if(st.top().second == k){
st.pop();
}
}
}
string res = "";
while(!st.empty()) {
pair<char,int> tmp = st.top(); st.pop();
for(int i = 0 ; i < tmp.second ; i ++){
res += tmp.first;
}
}
return res;
}
signed main(){
cin>>n>>k;
cin>>s1>>s2;
if(k == 1){
cout<<"Yes"<<endl;
return 0;
}
ss1 = get_tag(s1);
ss2 = get_tag(s2);
//cout<<ss1<<" "<<ss2<<endl;
if(ss1 == ss2) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
G. Pyramid
题意:数n层有多少个三角形。斜着的也算!!
思路:先手画几个。测试数据这么多,显然是要找O(1)的公式。刚开始想的是求边长为1的个数,为2的个数。。。然后加起来。越推越复杂。然后发现直接找答案的方程不就完了嘛。一个很经典的想法。
原数列:0,1,5,15,35,70,126,210…
做差:1,4,10,20,35,56…
再做差:3,4,5,6,7…
再做差:1,1,1,1…
终于相等了。做差n次之后相等。说明,答案计算公式的最高次是n+1次。
然后就带值解出系数就好了 ans = ax4+bx3+cx2+dx+e
不过有人直接找出 ans =n * (n + 1) * (n + 2) * (n + 3) / 24 ,amazing啊。
AC代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9+7;
int qpow(int a,int b){
int res =1;
int tmp = a;
while(b){
if(b&1) res = (res*tmp) %mod;
tmp = (tmp*tmp)%mod;
b >>= 1;
}
return res;
}
int cal(int n){
return ((((qpow(n, 4)+6*qpow(n, 3))%mod+11*qpow(n, 2))%mod+6*n)%mod)*qpow(24, mod-2)%mod;
}
signed main(){
int t;
scanf("%lld",&t);
for(int i = 0 ; i < t ; i ++){
int n;
scanf("%lld",&n);
printf("%lld\n",cal(n));
}
return 0;
}
I. Magic Potion
题意:n 个 hero。m 个 monster。每个英雄有一个可以击败的怪物列表。但是最多只能杀一个。然后还有k个魔法药水。魔法药水可以让一个 英雄 多击杀一个怪物。每个人最多喝一瓶。
思路1:显然是二分图匹配。但是最开始想的是。先求一个最大匹配,然后把匹配过的删掉,再匹配一次。和k取min。但是wa了。因为删掉一个点损失太大了,就没有了和别人匹配的机会。不过还好很快救回来了。把每个英雄拆成两个。然后再求最大匹配。再和 原答案+k 取min。交了一发过了。
思路2:跑最大流。只要建的一个好图。首先一个英雄杀一个怪,所以英雄到怪物的边流量为1的。其次,怪物最多挂掉一次。不可能反复死亡。所以怪物到超级汇点的流量为1。然后还有k瓶药水。 每瓶药水到只能给最多一个英雄。所以药水到英雄流量为1。但是只有k瓶。所以超级源点到药水的流量为k。应该不难理解。图示(s为超级源点,T为超级汇点):
AC代码1(二分图):
#include <bits/stdc++.h>
#define int long long
using namespace std;
/* ***************************************************
二分图匹配(匈牙利算法的DFS实现)
INIT:G[][]两边定点划分的情况
CALL:res=Hungary();输出最大匹配数
优点:适于稠密图,DFS找增广路快,实现简洁易于理解
时间复杂度:O(VE);
*****************************************************/
const int MAXN = 1010;
int uN,vN;//u,v数目
int G[MAXN][MAXN];//编号是1~n的
int linker[MAXN];
bool used[MAXN];
int mark[MAXN];
bool dfs(int u){
int v;
for(v=1;v<=vN;v++){
if(mark[v]) continue;
if(G[u][v]&&!used[v]){
used[v]=true;
if(linker[v]==-1||dfs(linker[v])){
linker[v]=u;
return true;
}
}
}
return false;
}
int Hungary()
{
int res=0;
int u;
memset(linker,-1,sizeof(linker));
for(u=1;u<=uN;u++){
memset(used,false,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
signed main(){
int n,m,k;
cin>>n>>m>>k;
uN = n; vN = m;
for(int i = 1 ; i <= n ;i ++){
int cnt; cin>>cnt;
for(int j = 0 ; j < cnt ; j ++){
int x;
cin>>x;
G[i][x] = 1;
G[i+n][x] = 1;
}
}
int res1 = Hungary();
uN = 2*n;
int res2 = Hungary();
int res = min(res2,res1+k);
cout<<res<<endl;
return 0;
}
AC代码2(最大流):
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e18;
const int maxn =1000+10;
struct Edge
{
int from,to,cap,flow;
Edge(){}
Edge(int f,int t,int c,int fl):from(f),to(t),cap(c),flow(fl){}
};
struct Dinic
{
int n,m,s,t;
vector<Edge> edges;
vector<int> G[maxn];
int cur[maxn];
int d[maxn];
bool vis[maxn];
void init(int n,int s,int t)
{
this->n=n, this->s=s, this->t=t;
edges.clear();
for(int i=0;i<n;i++) G[i].clear();
}
void AddEdge(int from,int to,int cap)
{
edges.push_back( Edge(from,to,cap,0) );
edges.push_back( Edge(to,from,0,0) );
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS()
{
queue<int> Q;
Q.push(s);
memset(vis,0,sizeof(vis));
d[s]=0;
vis[s]=true;
while(!Q.empty())
{
int x=Q.front(); Q.pop();
for(int i=0;i<G[x].size();++i)
{
Edge& e=edges[G[x][i]];
if(!vis[e.to] && e.cap>e.flow)
{
d[e.to]=1+d[x];
vis[e.to]=true;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a)
{
if(x==t || a==0) return a;
int flow=0,f;
for(int& i=cur[x];i<G[x].size();++i)
{
Edge& e=edges[G[x][i]];
if(d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow) ) )>0)
{
e.flow +=f;
edges[G[x][i]^1].flow -=f;
flow +=f;
a-=f;
if(a==0) break;
}
}
return flow;
}
int max_flow()
{
int ans=0;
while(BFS())
{
memset(cur,0,sizeof(cur));
ans += DFS(s,INF);
}
return ans;
}
}DC;
signed main(){
int n,m,k;
cin>>n>>m>>k;
int tot = n+m;
DC.init(tot+3,1,tot+3);
DC.AddEdge(1,2,k);
for(int i = 0 ; i < n ; i ++){
DC.AddEdge(1,i+3,1);
DC.AddEdge(2,i+3,1);
}
for(int i = 0 ; i < m ; i ++){
DC.AddEdge(n+3+i,tot+3,1);
}
for(int i = 0 ; i < n ; i ++){
int cnt; cin>>cnt;
for(int j = 0 ; j < cnt ; j ++){
int id; cin>>id;
DC.AddEdge(i+3,id+n+2,1);
}
}
cout<<DC.max_flow()<<endl;
}
J. Prime Game
题意:一个数组,求出所有子区间的数连乘之后的不同质因子的个数之和。
思路:对每个数进行质因子分解。然后计算每个质因子对最后和的贡献。考虑一个质因子同时出现在了 a[i] 和 a[j],那么在计算a[j]的时候,所有包含i的子区间贡献在计算i的时候就已经计算过了。所有那么新增的贡献,就是经过j,且不经过i的子区间的个数。那么也就是 (j-i)*(n-j+1)。如下:
数组:[2,3,5,3,7]
对于第一个3,所有有贡献的子区间是 [1,2],[1,3],[1,4],[1,5] , [2,2],[2,3],[2,4],[2,5]
对于第二个3,所有有贡献的子区间是[3,4],[3,5],[4,4],[4,5]
还有一个需要注意的地方就是质因子处理到 sqrt(n)就行了。
AC代码:
#include <bits/stdc++.h>
#define ll long long
//#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9+7;
const int MX = 1e6+7;
int prime[MX];
bool vis[MX];
int tot = 0;
void pre()
{
for(ll i = 2; i*i < MX; ++i) {
if(!vis[i])
prime[tot++] = i;
for(ll j = 0; j < tot && i * prime[j] < MX; ++j) {
vis[i*prime[j]] = 1;
if(i%prime[j] == 0)
break;
}
}
}
ll last[MX];
signed main(){
pre();
ll n;
while(cin>>n) {
ll ans = 0;
int tmp;
for(ll i = 1; i <= n; ++i) {
scanf("%d", &tmp);
for(ll j = 0; j < tot && prime[j]*prime[j] <= tmp; ++j) {
if(tmp%prime[j] == 0) {
while(tmp%prime[j] == 0) {
tmp /= prime[j];
}
ans = ans+((i-last[prime[j]])*(n-i+1));
last[prime[j]] = i;
}
}
if(tmp > 1){
//cout<<tmp<<endl;
ans = ans+((i-last[tmp])*(n-i+1));
last[tmp] = i;
}
}
cout<<ans<<endl;
}
return 0;
}
K. Kangaroo Puzzle
题意:一个地图。1的地方都是袋鼠。0都是墙。碰到障碍不能走。袋鼠可以叠在一起。可以用 UDLR 控制所有袋鼠同时移动。要在1e5次操作之内把他们全部移动到一个点。
思路:每次选择两个点。让其中一个去追另一个。直到最后只剩下1个。这样可以保证会走到一起。因为地图太小。操作次数也是很有限的。不会超。有点像CF div2 DE难度的样子。至于一个点追另一个点,就暴力模拟。先找到一条路径。然后放到队列里。然后开始追。一边进一边出。前面的总会碰到墙的。因为保证没有环。所以可以追到他无路可走。不过这题还有很多种思路也可以做。最强的就是下面贴的随机输出答案的。也能过。
AC代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9+7;
int n,m;
int mp[50][50];
int dirr[][2] = {0,1,1,0,0,-1,-1,0};
char dirrr[] = {'R','D','L','U'};
int mark[50][50];
map<char,int> id;
string res = "";
void show(){
return;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
cout<<mp[i][j];
}
cout<<endl;
}
}
bool check(){
int res = 0;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
res += mp[i][j] == 1;
if(res == 2) return false;
}
}
return true;
}
void move_kangaroo(char dir){
if(dir == 'U'){
for(int i = 1 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
if(mp[i][j] == 1 && mp[i-1][j] != 0){
mp[i-1][j] = 1;
mp[i][j] = 2;
}
}
}
}
if(dir == 'D'){
for(int i = n-2 ; i >= 0 ; i --){
for(int j = 0 ; j < m ; j ++){
if(mp[i][j] == 1 && mp[i+1][j] != 0){
mp[i+1][j] = 1;
mp[i][j] = 2;
}
}
}
}
if(dir == 'L'){
for(int i = 0 ; i < n ; i ++){
for(int j = 1 ; j < m ; j ++){
if(mp[i][j] == 1 && mp[i][j-1] != 0){
mp[i][j-1] = 1;
mp[i][j] = 2;
}
}
}
}
if(dir == 'R'){
for(int i = 0 ; i < n ; i ++){
for(int j = m-2 ; j >= 0 ; j --){
if(mp[i][j] == 1 && mp[i][j+1] != 0){
mp[i][j+1] = 1;
mp[i][j] = 2;
}
}
}
}
}
string dfs(int x1,int y1,int x2,int y2,string dir){
if(x1 == x2 && y1 == y2) return dir;
//cout<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<endl;
mark[x1][y1] = 1;
for(int i = 0 ; i < 4 ; i ++){
int x = x1+dirr[i][0];
int y = y1+dirr[i][1];
if( x >= 0 && x < n && y < m && y >= 0 && !mark[x][y] && mp[x][y]){
string dir2 = "";
dir2 += dirrr[i];
string tmp = dfs(x,y,x2,y2,dir2);
if(tmp != ""){
mark[x1][y1] = 0;
return dir+tmp;
}
}
}
mark[x1][y1] = 0;
string tmp = "";
return tmp;
}
void solve(){
int x1,x2,y1,y2;
while(!check()){
x1 = x2 = -1;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
if(mp[i][j] == 1 && x1 == -1){
x1 = i;
y1 = j;
}else if(mp[i][j] == 1){
x2 = i;
y2 = j;
}
}
}
string tmp = "";
string path = dfs(x1,y1,x2,y2,tmp);
show();
queue<char> que;
for(int i = 0 ; i < path.size() ; i ++){
que.push(path[i]);
}
while(que.size()){
char now = que.front();que.pop();
int tox2 = x2+dirr[id[now]][0];
int toy2 = y2+dirr[id[now]][1];
if(tox2 >= 0 && tox2 < n && toy2 >=0 && toy2 < m && mp[tox2][toy2]){
x2 = tox2;
y2 = toy2;
que.push(now);
}
x1 = x1+dirr[id[now]][0];
y1 = y1+dirr[id[now]][1];
move_kangaroo(now);
res += now;
if(x1 == x2 && y1 == y2) break;
show();
}
}
show();
}
signed main(){
id['R'] = 0;
id['D'] = 1;
id['L'] = 2;
id['U'] = 3;
cin>>n>>m;
for(int i = 0 ; i < n ; i ++){
string s;
cin>>s;
for(int j = 0 ; j < m ; j ++){
mp[i][j] = s[j] - '0';
}
}
solve();
cout<<res<<endl;
return 0;
}
这代码也能过。真 · 玄学。
#include<bits/stdc++.h>
using namespace std;
int n,m;
char tmp[23],d[4]={'L','R','U','D'};
int main()
{
srand(6510561);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",tmp);
for(int i=1;i<=50000;i++) printf("%c",d[rand()%4]);
}
M. Mediocre String Problem
题意:给定两个串s和t。三元组(i,j,k)表示 s[i-j] + t[0-k],有多少个三元组使得结果串是回文串。简单来说就是在s中找一个子串 ,与t的前缀连起来变成回文串。求方案数。
!思路参考:https://www.cnblogs.com/luowentao/p/10332309.html
思路1:个人觉得思路2更好理解得多。可以先看思路2。把s[i-j] + t[0-k] 拆成三部分看。 s1 + s2 + t1。其中s2不为空。因为题目要求 s1+s2 > t1。s1 = reverse(t1)。那么就要求s2为回文串。也就是要找出 s串的所有回文。然后枚举s1的左端点。回文串可以用Manacher求出来(也还有很多别的方法,回文自动机,字符串哈希)。然后要找s1 == reverse(t1)这个东西。 因为t1就是t的前缀,而s1可以看成是s串的某个后缀的前缀。也就是和t求LCP。如果把s串倒过来。 然后再和t跑一遍EXKMP。刚好就可以求出所有的LCP了。然后就是组合回文串和LCP。遍历一遍原串。以每一个位置i,当成是(i,j,k)里面的j。然后去枚举k。其实枚举k和枚举i是一样的。只需要枚举一个。那其实也就是枚举LCP。如果线性的去枚举。那肯定超时了。因为刚刚已经计算过所有位置的LCP。那么对LCP求一个前缀和。然后枚举的时候,只需要求 i 点 回文半径+1 内的所有LCP之和,就相当于枚举了所有的s2 与 s1。就可以求出答案了。难点是下标的计算。
AC代码1:
#include <iostream>
#include <bits/stdc++.h>
#include <unordered_map>
#define int long long
#define mk make_pair
#define gcd __gcd
using namespace std;
const double eps = 1e-10;
const int mod = 998244353;
const int N = 5e6+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N],c[N];
const int maxn=3e6+9; //字符串长度最大值
int nex[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算nex数组
void GETNEXT(char *str)
{
int i=0,j,po,len=strlen(str);
nex[0]=len;//初始化nex[0]
while(str[i]==str[i+1]&&i+1<len)//计算nex[1]
i++;
nex[1]=i;
po=1;//初始化po的位置
for(i=2;i<len;i++)
{
if(nex[i-po]+i<nex[po]+po)//第一种情况,可以直接得到nex[i]的值
nex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到nex[i]的值
{
j=nex[po]+po-i;
if(j<0)j=0;//如果i>po+nex[po],则要从头开始匹配
while(i+j<len&&str[j]==str[j+i])//计算nex[i]
j++;
nex[i]=j;
po=i;//更新po的位置
}
}
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
int i=0,j,po,len=strlen(s1),l2=strlen(s2);
GETNEXT(s2);//计算子串的nex数组
while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
i++;
ex[0]=i;
po=0;//初始化po的位置
for(i=1;i<len;i++)
{
if(nex[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
ex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到ex[i]的值
{
j=ex[po]+po-i;
if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
j++;
ex[i]=j;
po=i;//更新po的位置
}
}
}
char Ma[maxn*2]; // #号填充原串
int Mp[maxn*2]; // 回文半径
void Manacher(char s[],int len){
int l=0;
Ma[l++]='$';
Ma[l++]='#';
for(int i=0;i<len;i++){
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0; // mx 最右回文右边界
for(int i=0;i<l;i++){
Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
while(Ma[i+Mp[i]]==Ma[i-Mp[i]])Mp[i]++;
if(i+Mp[i]>mx){
mx=i+Mp[i];
id=i;
}
}
}
int sum[N];
int cal(int l,int r){
if(l > r) return 0;
if(l <= 0) return sum[r];
return sum[r]-sum[l-1];
}
char s1[N],s2[N];
signed main(){
cin>>s1>>s2;
int len1 = strlen(s1);
Manacher(s1,len1);
reverse(s1,s1+len1);
EXKMP(s1,s2);
reverse(ex,ex+len1);
sum[0] = ex[0];
for(int i = 1; i < len1 ; i ++) sum[i] = sum[i-1]+ex[i];
int res = 0;
for(int i = 2 ; i < 2*len1 + 3 ; i ++){
int now = Mp[i]-1; // 回文半径
if(now <= 0) continue;
if(now%2 == 1){ // 奇数长度
int center = (i-2)/2; // 计算回文中心
int r = center-1;
int l = center-Mp[i]/2;
res += cal(l,r);
}else{
int center = (i-2-1)/2;
int r = center-1;
int l = center-now/2;
res += cal(l,r);
}
}
cout<<res<<endl;
return 0;
}
思路2:第一步同思路1,把s[i-j]分成s1 和 s2 去枚举。不过这次是。固定 s1 的右端点。也是就说 s[i-j] 分成 s[i-p] + s[p+1-j] ,枚举这个p。p固定之后。i,j怎么枚举呢。i其实就是LCP的最大长度。也就是 EX[p]。因为每个长度都可以贡献一次嘛。而j就简单了。 j的个数 其实就是 以p+1为起点的回文串的个数。顺着求不是很好求。 但是如果把他倒过来看。也就是把s倒过来算。 那就是以p+1为终点的回文串的个数。也就是回文后缀的个数。这个就是PAM回文树求的东西。因为fail指针就是一个回文后缀。那么fail指针的高度。就是回文后缀的个数。也就是说在插入过程中。记录这个 height[p+1] 就是 以p+1为起点的回文串的个数。有点绕。 总之就是 把s串倒过来。 然后依次插入 回文树。 模板的num[i] 记录的就是回文后缀的个数。求完之后。再把他 num数组倒回来。就行了。然后求解答案的时候。枚举到 p,贡献就是 ex[p]*num[p+1]。
AC代码2:
#include <iostream>
#include <bits/stdc++.h>
#include <unordered_map>
#define int long long
#define mk make_pair
#define gcd __gcd
using namespace std;
const double eps = 1e-10;
const int mod = 998244353;
const int N = 2e6+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N],c[N];
int mark[N];
char s[N];
struct PAM{
/**
len[u] : u 节点代表回文串的长度。
fa[u] : u 节点代表回文串的最长回文后缀代表的节点。
tran[u][c] : 转移函数,表示在 u 代表的回文串的两端加上字符 c 之后的回文串。
num[u] : 代表 u 节点代表回文串的回文后缀个数。
L[i] : 代表原字符串以 i 结尾的回文后缀长度。
size[u] : u 点代表的回文串的数量。
**/
int len[N],fa[N],size[N],num[N],tot,last,trans[N][27],L[N];
int cnt[N];
void init(){ // 初始化
len[0]=0;fa[0]=1;len[1]=-1;fa[1]=0;
tot=1;last=0;
memset(trans[1],0,sizeof(trans[1]));
memset(trans[0],0,sizeof(trans[0]));
}
int new_node(int x){ // 建立新节点
int now=++tot;
memset(trans[tot],0,sizeof(trans[tot]));
len[now]=x;
return now;
}
int ins(int c,int n){ // 增量法构造
int u=last;
while(s[n-len[u]-1]!=s[n])u=fa[u];
if(trans[u][c]==0){
int now=new_node(len[u]+2);
int v=fa[u];
while(s[n-len[v]-1]!=s[n])v=fa[v];
fa[now]=trans[v][c];
trans[u][c]=now;
num[now]=num[fa[now]]+1;
}
last=trans[u][c];size[last]++;
L[n]=len[last];
cnt[n] = num[last];
return num[last];
}
void build(char *s){
int len = strlen(s);
for(int i = 0 ; i < len ; i ++){
ins(s[i]-'a',i);
}
}
}pam;
const int maxn=3e6+9; //字符串长度最大值
int nex[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算nex数组
void GETNEXT(char *str)
{
int i=0,j,po,len=strlen(str);
nex[0]=len;//初始化nex[0]
while(str[i]==str[i+1]&&i+1<len)//计算nex[1]
i++;
nex[1]=i;
po=1;//初始化po的位置
for(i=2;i<len;i++)
{
if(nex[i-po]+i<nex[po]+po)//第一种情况,可以直接得到nex[i]的值
nex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到nex[i]的值
{
j=nex[po]+po-i;
if(j<0)j=0;//如果i>po+nex[po],则要从头开始匹配
while(i+j<len&&str[j]==str[j+i])//计算nex[i]
j++;
nex[i]=j;
po=i;//更新po的位置
}
}
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
int i=0,j,po,len=strlen(s1),l2=strlen(s2);
GETNEXT(s2);//计算子串的nex数组
while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
i++;
ex[0]=i;
po=0;//初始化po的位置
for(i=1;i<len;i++)
{
if(nex[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
ex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到ex[i]的值
{
j=ex[po]+po-i;
if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
j++;
ex[i]=j;
po=i;//更新po的位置
}
}
}
char s1[N],s2[N];
int cnt[N];
signed main(){
cin>>s1>>s2;
int len1 = strlen(s1);
int len2 = strlen(s2);
strcpy(s,s1);
reverse(s,s+len1);
pam.init();
pam.build(s);
for(int i = 0 ; i < len1 ; i ++){
cnt[i] = pam.cnt[len1-1-i];
}
reverse(s1,s1+len1);
EXKMP(s1,s2);
reverse(ex,ex+len1);
int res = 0;
for(int i = 0 ; i < len1-1 ; i ++){
res += ex[i]*cnt[i+1];
}
cout<<res<<endl;
return 0;
}