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,并在每个位置打上
个标记,代表破了那堵墙,并优先更新破墙数少的状态
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(
)的,用bitset能优化到O(
/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;
}