2022杭电多校第一场

A String
题意:求s串1-i(1 <i <n)子串的贡献,贡献为公共前后缀相交并且相交部分长度为k的倍数的数量
题解做法是用exkmp求出s串与所有后缀的LCP后,设LCP为x,那么当x+i-1大于2*(i-1)时,有公共前后缀相交,而却会对2*(i-1)+k,2*(i-1)+2*k...等位置上有贡献,所以做个模意义上的差分。
还有一种做法是在kmp树上维护cnt[2*x%k],因为(2*x-len)%k==0,等价于2*x%k==len%k,计算答案时就是cnt[len%k],然后还要满足有相交的条件,可以在树链上二分查找(学长的做法),也可以用带限制的kmp求链顶。
比赛时没细想,原来还有exkmp比kmp方便的题,长见识
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
#define int long long
#define mem(x,y) memset(x,y,sizeof(x));
const int maxn=5e6+10;
const int mod=998244353;

int n,k;
string s;
int z[maxn],f[maxn];

void init()
{
    int l=1,r=0;
    for(int i=1;i<n;++i)
    {
        int k=0;
        if(i<=r)k=min(z[i-l],r-i+1);
        while(i+k<n&&s[k]==s[i+k])++k;
        z[i]=k;
        if(i+k-1>r)
        {
            l=i;
            r=i+k-1;
        }
    }
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    while(_--)
    {
        mem(f,0);
        cin>>s>>k;
        n=s.size();
        init();
        for(int i=0;i<n;++i)
        {
            if(2*i-1+k<=i+z[i]-1)++f[2*i-1+k];
            else continue;
            int t=(i+z[i]-1-2*i+1)/k;
            --f[min(maxn,2*i-1+k*(t+1))];
        }
        // for(int i=0;i<n;++i)
        // cout<<f[i]<<" ";cout<<'\n';
        for(int i=0;i<n;++i)
        if(i-k>=0)f[i]+=f[i-k];
        for(int i=0;i<n;++i)
        if((i+1)%k==0)++f[i];
        int ans=1;
        for(int i=0;i<n;++i)
        ans=ans*(f[i]+1)%mod;
        cout<<ans<<'\n';
    }
    return 0;
}
B Dragon slayer
题意:地图上n堵墙,从起点到终点最少需要撞破多少堵
因为n最多只有15,所以直接bfs,并在每个位置打上 2^n个标记,代表破了那堵墙,并优先更新破墙数少的状态
tips:可以把所有数×2避免处理double
比赛时,数组开大了,MLE了两发……
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define mem(x,y) memset(x,y,sizeof(x))
const int maxn=1<<16;
const int nx[4]={0,1,0,-1};
const int ny[4]={1,0,-1,0};

struct Node
{
    int x,y;
    int sta,cnt=0;
    bool operator<(const Node &node)const
    {
        return cnt>node.cnt;
    }
};

int n,m,k;
int sx,sy,tx,ty;
bool vis[16][16][maxn];
int a[35][35];

int bfs()
{
    mem(vis,0);
    priority_queue<Node> q;
    Node it;
    it.x=2*sx+1;it.y=2*sy+1;it.sta=0;it.cnt=0;
    q.push(it);
    while(!q.empty())
    {
        Node fr=q.top();
        q.pop();
        if(vis[(fr.x-1)/2][(fr.y-1)/2][fr.sta])continue;
        vis[(fr.x-1)/2][(fr.y-1)/2][fr.sta]=1;
        if(fr.x==2*tx+1&&fr.y==2*ty+1)return fr.cnt;
        for(int i=0;i<4;++i)
        {
            Node it=fr;
            int xx=fr.x+nx[i],yy=fr.y+ny[i];
            if(xx<=0||xx>=2*n||yy<=0||yy>=2*m)continue;
            if(a[xx][yy]&&((fr.sta>>a[xx][yy])&1)==0)
            {
                it.sta|=(1<<a[xx][yy]);
                ++it.cnt;
            }
            it.x+=2*nx[i];it.y+=2*ny[i];
            q.push(it);
        }
    }
    return -1;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    while(_--)
    {
        mem(a,0);
        cin>>n>>m>>k;
        cin>>sx>>sy>>tx>>ty;
        int x1,y1,x2,y2;
        for(int i=1;i<=k;++i)
        {
            cin>>x1>>y1>>x2>>y2;
            if(x1==x2)
            {
                for(int j=2*min(y1,y2);j<=2*max(y1,y2);++j)
                a[2*x1][j]=i;
            }
            if(y1==y2)
            {
                for(int j=2*min(x1,x2);j<=2*max(x1,x2);++j)
                a[j][2*y1]=i;
            }
        }
        cout<<bfs()<<'\n';
    }
    return 0;
}
C Backpack
题意:n个物品,m容量的背包,价值是异或和,求正好装满m的最大价值
dp[i][j]表示i答案j容量的状态能否到达,dp[i^v][j+w]=dp[i^v][j+w] | dp[i][j],这样是O(n^3)的,用bitset能优化到O( n^3 /32)
神奇的bitset
#include<iostream>
#include<bitset>
#include<cstring>
using namespace std;
#define mem(x,y) memset(x,y,sizeof(x))
const int maxn=1<<10;

int n,m;
int v[maxn],w[maxn];
bitset<maxn> dp[2][maxn];

int solve()
{
    mem(dp,0);
    dp[0][0][0]=1;
    int tot=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<(1<<10);++j)
        dp[tot^1][j]=dp[tot][j];
        for(int j=0;j<(1<<10);++j)
        {
            dp[tot^1][j^v[i]]|=(dp[tot][j]<<w[i]);
        }
        tot^=1;
    }
    // for(int j=0;j<=14;++j)
    // {
    //     for(int i=0;i<=m;++i)
    //     cout<<dp[tot][i][j]<<" ";
    //     cout<<'\n';
    // }
    for(int j=(1<<10)-1;j>=0;--j)
    if(dp[tot][j][m])return j;
    return -1;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;++i)
        cin>>w[i]>>v[i];
        cout<<solve()<<'\n';
    }
    return 0;
}
 
D Ball
题意:有n个棋子,有几对三个棋子的曼哈顿距离的中位数是素数
可以预处理出所有棋子两两的曼哈顿距离,排序,然后每处理一条边,就打上标记,因为是完全图,所以如果当前的fr,to有一个点x,vis[fr][x],vis[to][x]其中有一个打了标记,那就以为着fr-x,to-x其中有一条边是大于fr-to的,一条边是小于fr-to的,也就是fr-to是中位数,所以只要是fr-to是素数,就统计答案,发现这个其实是异或,所以也可以用bitset优化
又是bitset
#include<iostream>
#include<bitset>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
#define debug(x) cout<<#x<<" "<<(x)<<endl
#define mem(x,y) memset(x,y,sizeof(x))
const int maxn=2e3+10;
const int maxm=3e5+10;

struct Edge
{
    int fr,to,w;
    bool operator<(const Edge &edge)const
    {
        return w<edge.w;
    }
};

int n,m;
int x[maxn],y[maxn];
Edge dis[maxn*maxn];
bitset<maxn> edg[maxn];
int tot=0,pi[maxn];
bool isp[maxm];

void init()
{
    mem(isp,1);
    isp[1]=0;
    for(int i=2;i<maxm;++i)
    {
        if(isp[i])pi[++tot]=i;
        for(int j=1;j<=tot&&i*pi[j]<maxm;++j)
        {
            isp[i*pi[j]]=0;
            if(i%pi[j]==0)break;
        }
    }
}

int solve()
{
    for(int i=1;i<=n;++i)
    edg[i].reset();
    int tot=0;
    for(int i=1;i<=n;++i)
    for(int j=1;j<i;++j)
    dis[++tot]={i,j,abs(x[i]-x[j])+abs(y[i]-y[j])};
    sort(dis+1,dis+1+tot);
    int ans=0;
    for(int i=1;i<=tot;++i)
    {
        int fr=dis[i].fr,to=dis[i].to;
        if(isp[dis[i].w])ans+=(edg[fr]^edg[to]).count();
        edg[fr][to]=1;edg[to][fr]=1;
    }
    return ans;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    init();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;++i)
        cin>>x[i]>>y[i];
        cout<<solve()<<'\n';
    }
    return 0;
}
H Path
题意:有一些特殊的边,在走过以后,走到相邻的点会值得花费-k,走到不相邻的点花费为0(好奇怪)
因为边权都是大于k的,没有负边权,所以跑dijkstra,每个点打两次标记,代表上次是不是特殊边,再用一个set维护还没有通过特殊边传送的点,因为这次传送肯定比下一次传送更优,所有对于每个点,传送只会更新一次,复杂度是可以接受的
可能题面有点奇怪,开的人不多
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<vector>
#include<set>
#include<queue>
using namespace std;
#define debug(x) cout<<#x<<" "<<(x)<<endl
#define debug2(x,y) cout<<#x<<endl;for(int i=1;i<=y;++i)cout<<x[i]<<" ";cout<<endl
#define mem(x,y) memset(x,y,sizeof(x));
#define int long long
#define double long double
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
const double PI=acos(-1.0);
const int maxn=1e6+10;

struct Edge
{
    int to,w,flg;
    bool operator<(const Edge &edge)const
    {
        return w>edge.w;
    }
};

int n,m,s,k;
vector<Edge> edg[maxn];
bool vis[maxn][2];
int dis[maxn][2];
int dfn[maxn];

void dijkstra()
{
    mem(vis,0);
    mem(dis,0x3f);
    mem(dfn,0);
    set<int> ss;
    for(int i=1;i<=n;++i)
    if(i!=s)ss.insert(i);
    priority_queue<Edge> q;
    dis[s][0]=0;
    q.push({s,0,0});
    int tot=0;
    while(!q.empty())
    {
        int fr=q.top().to,flg=q.top().flg;
        q.pop();
        if(vis[fr][flg])continue;
        vis[fr][flg]=1;
        ss.erase(fr);
        ++tot;
        if(flg)
        {
            for(auto it:edg[fr])
            dfn[it.to]=tot;
            vector<int> t;
            for(int it:ss)
            {
                if(dfn[it]==tot)continue;
                dis[it][0]=dis[fr][1];
                q.push({it,dis[it][0],0});
                t.push_back(it);
            }
            for(int it:t)ss.erase(it);
        }
        int t=0;
        if(flg)t=k;
        for(auto it:edg[fr])
        {
            if(dis[it.to][it.flg]<dis[fr][flg]+it.w-t)continue;
            dis[it.to][it.flg]=dis[fr][flg]+it.w-t;
            q.push({it.to,dis[it.to][it.flg],it.flg});
        }
    }
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // cout<<fixed<<setprecision(6);
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>m>>s>>k;
        for(int i=1;i<=n;++i)
        edg[i].clear();
        int fr,to,w,flg;
        for(int i=1;i<=m;++i)
        {
            cin>>fr>>to>>w>>flg;
            edg[fr].push_back({to,w,flg});
        }
        dijkstra();
        for(int i=1;i<=n;++i)
        {
            int t=min(dis[i][0],dis[i][1]);
            if(t!=dis[0][0])cout<<t<<" ";
            else cout<<-1<<" ";
        }
        cout<<'\n';
    }
    return 0;
}
I Laser
题面:有n个敌人,有一架米字炮,问能不能一次把所有敌人消灭
选两个点枚举他们在米字炮的哪两条边上,12种情况,还有在同一条线上的情况,那就再找第三个不在这条线上的点,3种情况
比赛时一开始的做法复杂了,写了一百五十几行才发现前面的检验都不需要,重写的时候也没想到怎么减少码量,总之体力被榨干了
#include<iostream>
using namespace std;
const int maxn=1e6+10;

int n;
int x[maxn],y[maxn];

bool check(int xx,int yy)
{
    for(int i=1;i<=n;++i)
    {
        if(xx==x[i]||yy==y[i]||xx-yy==x[i]-y[i]||xx+yy==x[i]+y[i])continue;
        return 0;
    }
    return 1;
}

bool big_check(int x1,int y1,int x2,int y2)
{
    int b1,b2;
    if(check(x2,y1))return 1;
    if(check(x2-(y1-y2),y1))return 1;
    if(check(x2+(y1-y2),y1))return 1;

    if(check(x1,y2))return 1;
    if(check(x1,y2-(x2-x1)))return 1;
    if(check(x1,y2+(x2-x1)))return 1;

    b1=y1-x1;
    if(check(y2-b1,y2))return 1;
    if(check(x2,x2+b1))return 1;
    b2=y2+x2;
    if(check((b2-b1)/2,(b1+b2)/2))return 1;

    b1=y1+x1;
    if(check(-y2+b1,y2))return 1;
    if(check(x2,-x2+b1))return 1;
    b2=y2-x2;
    if(check((b1-b2)/2,(b1+b2)/2))return 1;
    return 0;
}

bool solve()
{
    if(n<=2)return 1;
    if(big_check(x[1],y[1],x[2],y[2]))return 1;
    int x1=x[1],y1=y[1],x2=x[2],y2=y[2];
    if(x1==x2)
    {
        int t=-1;
        for(int i=3;i<=n;++i)
        if(x[i]!=x1)t=i;
        if(t==-1)return 1;
        else if(big_check(x1,y1,x[t],y[t]))return 1;
    }
    if(y1==y2)
    {
        int t=-1;
        for(int i=3;i<=n;++i)
        if(y[i]!=y1)t=i;
        if(t==-1)return 1;
        else if(big_check(x1,y1,x[t],y[t]))return 1;
    }
    if(y1+x1==y2+x2)
    {
        int t=-1;
        for(int i=3;i<=n;++i)
        if(y[i]+x[i]!=y1+x1)t=i;
        if(t==-1)return 1;
        else if(big_check(x1,y1,x[t],y[t]))return 1;
    }
    if(y1-x1==y2-x2)
    {
        int t=-1;
        for(int i=3;i<=n;++i)
        if(y[i]-x[i]!=y1-x1)t=i;
        if(t==-1)return 1;
        else if(big_check(x1,y1,x[t],y[t]))return 1;
    }
    return 0;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n;
        for(int i=1;i<=n;++i)
        {
            cin>>x[i]>>y[i];
            x[i]*=2;
            y[i]*=2;
        }
        if(solve())cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}
K Random
题意:有n个0到1的随机数,删除m个数,1/2概率删除最大值,1/2概率删除最小值
n个数的期望是0.5,因为一半概率删最大,一半概率删最小,所以期望还是0.5,剩下n-m个数,答案就是(n-m)/2,再求一下逆元
#include<iostream>
#include<cstring>
using namespace std;
#define int long long
#define debug(x) cout<<#x<<": "<<(x)<<endl
#define mem(x,y) memset(x,y,sizeof(x))
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;

int bitpow(int x,int y)
{
    int res=1;
    while(y)
    {
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    int inv2=bitpow(2,mod-2);
    while(_--)
    {
        int x,y;
        cin>>x>>y;
        cout<<(x-y+mod)*inv2%mod<<'\n';
    }
    return 0;
}
L Alice and Bob
题面:有一些数,Alice分成两堆,Bob选一堆删除,另一堆所有数-1,场上有0Alice就赢,问谁赢
2个1或者4个2或者……可以赢,然后一个数等价于前面的半个数
博弈论不太懂,反正交给队友了,因为队友太强了
#include<iostream>
using namespace std;
#define int long long
const int maxn=1e6+10;

int n;
int a[maxn];

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n;
        for(int i=0;i<=n;++i)
        cin>>a[i];
        bool flg=0;
        for(int i=n;i>=0;--i)
        {
            if(a[i]>>i)
            {
                flg=1;
                break;
            }
            if(i)a[i-1]+=a[i]/2;
        }
        if(flg)cout<<"Alice\n";
        else cout<<"Bob\n";
    }
    return 0;
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值