String(扩展KMP、差分)
Problem
给定一个字符串\(S\),定义\(F_S\)表示满足如下条件的正整数\(x\)的数量
- \(1\le x\le |S|\)
- \(S[1,\cdots,x]=S[|S|-x+1,|S|]\)
- \(S[1,\cdots,x]\)和\(S[|S|-x+1,|S|]\)的交的长度不为\(0\)且可以被\(k\)整除
现在要求\(\prod_{i=1}^{|S|}(F_{S[1,\cdots,i]}+1)\)
\(1\le |S|\le 10^6\)
Solve
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
string s;
cin>>s;
int n=s.size();
int k;
cin>>k;
vector<int>z(n),sum(n+1);
z[0]=n;
for(int i=1,l=0,r=0;i<n;i++){
if(i<=r && z[i-l]<r-i+1) z[i]=z[i-l];
else z[i]=max(0,r-i+1);
while(i+z[i]<n && s[z[i]]==s[i+z[i]]) ++z[i];
if(i+z[i]-1>r) l=i,r=i+z[i]-1;
}
ll ans=1;
for(int i=0;i<n;i++){
int x=z[i];
if(x>=i+k){
x=((x-i)/k)*k;
sum[2*i+k]++;
if(2*i+k+x<=n) sum[2*i+k+x]--;
}
}
for(int i=1;i<=n;i++){
if(i+k<=n) sum[i+k]+=sum[i];
ans=ans*(sum[i]+1)%mod;
}
cout<<ans<<'\n';
}
}
Dragon slayer(暴力枚举判断、模拟)
Backpack(背包、bitset 优化空间、暴力)
Problem
有\(n\)个物品,每个物品有价值\(v\)和重量\(w\),现在有一个背包容量为\(m\),问在把背包刚好装满的情况下,物品价值异或值最大是多少
\(1\le n,m\le 2^{10}\)
\(1\le v,w\le 2^{10}\)
Solve
这题会卡空间,首先很容易想到\(O(n^3)\)的做法,用\(dp[i][j]\)表示背包容量为\(i\),价值异或和为\(j\)是否可以转移到,用滚动数组优化一下,就是
dp[2][i][j];
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++)
for(int j=0;j<=1024;j++)
dp[1][j][k]|=dp[0][j-w[i]][k^v[i]];
}
但这样空间会炸,所以考虑用bitset
优化
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,m;
cin>>n>>m;
vector<bitset<1024>>f1(1024),f2(1024);
f1[0][0]=1;
for(int i=1;i<=n;i++){
int v,w;
cin>>v>>w;
for(int j=0;j<1024;j++){
f2[j]=f1[j]|(f1[j^w]<<v);
}
for(int j=0;j<1024;j++)
f1[j]=f2[j];
}
bool ok=0;
for(int i=1023;i>=0;i--){
if(f1[i][m]){
cout<<i<<'\n';
ok=1;
break;
}
}
if(!ok) cout<<-1<<'\n';
}
}
Ball(bitset优化)
Problem
在\(M\times M\)的平面上有\(N\)个点,求有多少三个无序点对的曼哈顿距离的中位数是质数
曼哈顿距离:\(|x_1-x_2|+|y_1-y_2|\)
\(1\le N\le 2000\),\(1\le M\le 10^5\)
Solve
暴力做法是\(O(n^3)\)的,这种问题一般先转化成求每个是素数的距离对答案的贡献。我们先把所有二元点对按照距离排序,有\(O(n^2)\)个,然后按照距离从小到大考虑。由于我们从小到大考虑,并且求的是中位数,所以我们需要知道对于当前的边的两个点\(u,v\),有多少个点到达\(u\)的距离小于当前距离,有多少个点的距离到达\(v\)大于当前距离(反过来考虑也是一样的)。注意到我们从小到大枚举,那么在之后的枚举过程中,之前的边都会小于当前边,所以我们在枚举过程中更新"有多少个点到达\(u\)的距离小于当前距离",而这里的当前距离是对于后面边而言。而"有多少个点的距离到达\(v\)大于当前距离"我们在加边的时候更新,而后面枚举距离的时候不断减小这个点集的数量。类似于双指针的思想。更新过程和求点数过程都是\(O(\frac{n}{w})\),故总时间复杂度是\(O(\frac{n^3}{w})\)
具体求点数就是\((\text{large}[u]\&\text{small}[v]).count()\),这样表示\(u,v\)都能到达的点,并且满足到\(u\)的距离大于当前边,到\(v\)的距离小于当前边。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int M=2e5+10,N=2005;
int primes[M],st[M],cnt;
void get_prime(){
st[1]=1;
for(int i=2;i<=200000;i++){
if(!st[i]) primes[++cnt]=i;
for(int j=1;j<=cnt&&primes[j]*i<=200000;j++){
st[primes[j]*i]=1;
if(i%primes[j]==0) break;
}
}
}
struct edges{
int u,v,dis;
bool operator < (const edges &t)const{
return dis<t.dis;
}
}e[N*N];
bitset<2005>small[2005],large[2005];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
get_prime();
while(T--){
int idx=0;
int n,m;
cin>>n>>m;
vector<int>x(n+1),y(n+1);
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i];
small[i].reset(),large[i].reset();
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
int dis=abs(x[i]-x[j])+abs(y[i]-y[j]);
e[++idx]={i,j,dis};
large[i].set(j),large[j].set(i);
}
sort(e+1,e+1+idx);
ll ans=0;
for(int i=1;i<=idx;i++){
int u=e[i].u,v=e[i].v,dis=e[i].dis;
if(!st[dis]) ans+=(large[u]&small[v]).count()+(large[v]&small[u]).count();
large[u].set(v,0),large[v].set(u,0);
small[u].set(v,1),small[v].set(u,1);
}
cout<<ans<<'\n';
}
return 0;
}
Travel plan(图论、莫比乌斯反演、数论)
Treasure(克鲁斯卡尔重构树)
Path(图论、分层图、BFS)
Problem
给定\(n\)个点\(m\)条边的有向图,每条边有边权,其中有一些特殊边和普通边,如果\(u\)经过特殊边到达\(v\),那么下次\(v\)可以到达任意点,如果这个点不与\(v\)相邻,那么花费\(0\),如果和\(v\)相邻,假设邻边的边权为\(w\),那么花费\(w-k\)。问从\(S\)出发,把每个点都遍历一遍,每个点需要的最少花费是多少,如果不能遍历到,输出\(-1\)。
\(1\le n,m\le 10^6\)
Solve
- 分层图
- BFS
在\(BFS\)队列中,记录每个点上一次是由什么边转移过来的,类似于分层图的思想,根据转移过来边的类型分类进行下一步转移。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
const ll inf=1e18;
struct edges{
int v,w,t,nxt;
}e[N];
int head[N],cnt,tag[N];
void add(int u,int v,int w,int t){
e[cnt]={v,w,t,head[u]},head[u]=cnt++;
}
struct node{
int u; ll d; int p;
bool operator < (const node&t)const{
return d>t.d;
}
};
ll dist[N][2];
bool st[N][2];
int n,m,S,K;
void bfs(){
set<int>s;
for(int i=1;i<=n;i++){
if(i!=S) s.insert(i);
dist[i][0]=dist[i][1]=inf;
st[i][0]=st[i][1]=false;
}
priority_queue<node>q;
q.push({S,0,0});
dist[S][0]=0;
int idx=0;
while(q.size()){
auto t=q.top();
int u=t.u;
q.pop();
idx++;
if(!t.p) s.erase(u); //由普通路径更新得到,直接删除
else{ //上一次的点是经过特殊边得到的,那么它下次可以到达任意点,这里先转移与它不相邻的点
for(int i=head[u];~i;i=e[i].nxt){
int v=e[i].v;
tag[v]=idx; //把与经过特殊边得到的点的相邻点标记
}
vector<int>tt;
for(auto v:s){ //更新所有没有更新过并且不与当前点相邻的点
if(tag[v]!=idx){
tt.push_back(v);
dist[v][0]=dist[u][0];
q.push({v,dist[v][0],0}); //重置为不是由特殊边更新得到
}
}
for(auto v:tt) s.erase(v); //更新了,在待更新队列中删除
}
int delta=0;
if(t.p) delta-=K;
if(st[u][t.p]) continue;
st[u][t.p]=true;
//更新邻边的点(普通边和特殊边一起考虑)
for(int i=head[u];~i;i=e[i].nxt){
int v=e[i].v,w=e[i].w,type=e[i].t;
if(dist[v][type]>dist[u][t.p]+w+delta){
dist[v][type]=dist[u][t.p]+w+delta;
q.push({v,dist[v][type],type});
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
memset(head,-1,sizeof head);
memset(tag,0,sizeof tag);
cnt=0;
cin>>n>>m>>S>>K;
for(int i=1;i<=m;i++){
int u,v,w,t;
cin>>u>>v>>w>>t;
add(u,v,w,t);
}
bfs();
for(int i=1;i<=n;i++){
ll d=min(dist[i][0],dist[i][1]);
if(d==inf) cout<<-1<<" ";
else cout<<d<<" ";
}
cout<<'\n';
}
}
Laser(计算几何、暴力check)
Problem
二维平面上有\(n\)敌人,现在可以在任何一个位置放置一个激光发射器,这个激光发射器会以米字的形式发出激光,在激光上的敌人会被消灭,询问是否可以只放置一个激光发射器就把所有敌人消灭
\(1\le n\le 5\times 10^5\)
Solve
激光发射方向有\(4\)个,即横线 竖线 右斜线 左斜线。如果存在这样一个点,那么每个敌人肯定在这个点的某一个方向上,由于两条直线可以确定一个点,可以暴力枚举这两个条线然后暴力判断。可以先确定第一个点所在的直线,然后找到一个和它不在一条直线上的点\(k\),再枚举点\(k\)所在的直线,现在就有两个直线了,可以确定一个激光点,然后再去判断剩下\(n-2\)个点是否在激光点的\(4\)个方向上即可。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+10;
struct Point{
int x,y;
Point(){}
Point(int x_,int y_):x(x_),y(y_){}
Point operator + (const Point &t)const{
return {x+t.x,y+t.y};
}
Point operator - (const Point &t)const{
return {x-t.x,y-t.y};
}
double operator * (const Point&t)const{
return x*t.x+y*t.y;
}
double operator ^ (const Point&t)const{
return x*t.y-y*t.x;
}
Point operator * (const double t)const{
return {x*t,y*t};
}
}p[N];
//横线 竖线 右斜线 左斜线
Point d[]={{0,1},{1,0},{1,1},{1,-1}}; //点向式表示直线
int n,k;
bool check(Point c){
for(int i=2;i<=n;i++){
if(i==k) continue;
int dx=p[i].x-c.x,dy=p[i].y-c.y;
if(abs(dx)!=abs(dy) && dx*dy!=0) return false;
}
return true;
}
//点向式
Point get_intersection(Point p1,Point v1,Point p2,Point v2){
Point u=p1-p2;
double t=(v2^u)/(v1^v2);
return p1+v1*t;
}
bool solve(){
for(int i=0;i<4;i++){
for(k=2;k<=n;k++){
if(((p[k]-p[1])^d[i])!=0){ //点k和点1不在一条直线上
break;
}
}
if(k==n+1) return true;
for(int j=0;j<4;j++){ //枚举点k所在的直线
if(i==j) continue;
Point c=get_intersection(p[1],d[i],p[k],d[j]);
if(check(c)) return true;
}
}
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;
if(solve()) cout<<"YES\n";
else cout<<"NO\n";
}
}
Random(概率论)
Problem
\(n\)个实数在区间\([0,1]\)中随机生成,接着进行\(m\)次操作,每次操作有\(\frac{1}{2}\)的概率删掉最大的数,有\(\frac{1}{2}\)的概率删掉最小的数。求最后剩下的数的和的期望。
Solve
\(E(x)=\int_0^1x\cdot 1dx=\frac{1}{2}\),从\(n\)个数中随机以\(\frac{1}{2}\)的概率\(m\)次删掉最大数或最小数,可以看做随机选择了\(n-m\)个数。期望总和写成\(\sum_{i=1}^{n-m}a_ip_i\),这是什么形式?就是期望啊,所以答案就是\(\frac{n-m}{2}\)
Code
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
int inv2=mod/2+1;
while(T--){
int n,m;
cin>>n>>m;
cout<<1LL*(n-m)*inv2%mod<<'\n';
}
return 0;
}
Alice and Bob(博弈论)
Problem
一开始黑板上有\(n+1\)个数,现在\(\text{Alice}\)和\(\text{Bob}\)轮流操作。\(\text{Alice}\)每次可以把黑板上还有的数分成\(2\)部分,\(\text{Bob}\)可以在\(\text{Alice}\)分成的\(2\)份中选择一份从黑白上擦除,然后把另一部分的数都减\(1\)。如果黑板上出现数字\(0\)则\(\text{Alice}\)赢,如果\(\text{Bob}\)把数字都擦除完了,那么\(\text{Bob}\)赢。
Solve
博弈问题一般先考虑最小的必胜态或必败态。容易发现只有\(1,1\)时是必胜态,\(2,2,2,2\)是必胜态,因为$$\text{Alice}\(可以平分然后\)\text{Bob}$无论如何操作都会变成\(1,1\)这个必胜态。
发现如果有\(2^i\)个\(i\),相当于一个\(i\),只需判断最后能否得到\(0\)即可。
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
vector<int>a(n+1);
for(int i=0;i<=n;i++) cin>>a[i];
for(int i=n;i>=1;i--) a[i-1]+=a[i]/2;
if(a[0]) cout<<"Alice\n";
else cout<<"Bob\n";
}
}
标签:HDU,le,const,Point,int,text,多校,cin,2022
From: https://www.cnblogs.com/Arashimu0x7f/p/16607888.html