2018 ICPC Nanjing Regional Contest

补题进度: 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;
}


总结:四个小时出5题。打的还不错了。不过没仔细检查就交。罚时太爆炸了。队伍沟通还是要多一些。都在各写各的。还是浪费了挺多时间。还有就是这场题解法太多了。要继续尝试用所有这些可行方法再AC一遍。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UIUC ICPC Spring Coding Contest 2019是UIUC(伊利诺伊大学厄巴纳-香槟分校)举办的一个编程比赛。UIUC ICPC Spring Coding Contest 2019是ACM国际大学生程序设计竞赛(ACM International Collegiate Programming Contest)的一部分。ACM国际大学生程序设计竞赛是世界上最具影响力的大学生计算机竞赛之一,每年吸引了来自全球各地的大学生参与。这个比赛旨在培养学生的算法和编程技能,提供一个展示和交流的平台。参赛者需要在规定时间内解决一系列编程问题。 参加UIUC ICPC Spring Coding Contest 2019对于那些对算法和编程有兴趣的学生来说,是一个很好的学习和锻炼机会。比赛中的问题通常涉及各种算法和数据结构,要求参赛者能够用编程语言实现有效和高效的解决方案。参赛者可以通过解决问题来提高他们的算法和编程技能,并与其他参赛者交流和学习。 在准备UIUC ICPC Spring Coding Contest 2019之前,建议参赛者先掌握一些基本的编程知识和技能,如数据结构、算法、编程语言等。参赛者可以参考一些相关的教程和学习资料,如GeeksforGeeks和HackerEarth等网站提供的编程教程。此外,还可以参考一些竞赛经验分享的文章和博客,了解其他人是如何准备和参加编程比赛的。 总之,参加UIUC ICPC Spring Coding Contest 2019是一个很好的机会,可以提高算法和编程技能,与其他参赛者交流和学习。准备比赛前,建议参赛者掌握基本的编程知识和技能,并参考一些相关的教程和学习资料。祝你在比赛中取得好成绩!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Awesome Competitive Programming Awesome](https://blog.csdn.net/qq_27009517/article/details/86593200)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值