起床困难综合症
(传送门)
题意
在[0,m]中取一个整数,使得n次给定的位运算操作后(and,or,xor),答案最大(m<=10^9,n<=10^5)
分析
位运算水题,按位考虑就行,O(nlogm)随便搞搞就行
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int n,m;
long long sum,ans;
int op[maxn],dat[maxn];
int f[101];
int cal(int x)
{
for(int i=1;i<=n;i++)
{
if(op[i]==1) x=(x&dat[i]);
else if(op[i]==2) x=(x|dat[i]);
else if(op[i]==3) x=(x^dat[i]);
}
return x;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
char ch[5];
long long x;
scanf("%s%d",ch,&x);
dat[i]=x;
if(ch[0]=='A') op[i]=1;
else if(ch[0]=='O') op[i]=2;
else if(ch[0]=='X') op[i]=3;
}
int t=cal(0);
for(int i=0;i<=30;i++)
f[i]=cal(1<<i)&(1<<i);
for(int i=30;i>=0;i--)
{
int now=1<<i;
if(t&now)ans+=now;
else if(f[i] && sum+now<=m)
{
sum+=now;
ans+=now;
}
}
cout<<ans<<endl;
}
魔法森林
(传送门)
题意
无向图每条边上有权值A,B,找出一条1->n的路径,使路径上所有边A的最大值与B的最大值之和尽量小。(n<=50000,m<=100000,Ai,Bi<=50000。)
分析1
官方正解是LCT。
n个点m条边,在LCT里,下标[1,n]是结点,下标[n+1,m+n]中的结点是边;边对应的结点才有权值(b)。
边按照a排序,并查集维护图的连通性。按a值从小到大不断地加边,维护并查集,加一条边时,要同时在LCT里连接这条边的两个点u和v(先连接u和边对应的结点,再把边对应的结点和v相连)。会连成环,看LCT中u到v路径b最大的边,删掉,加入这条边。
ans=min{a+LCT中起点到终点最大的b}。
代码1
/*
n个点m条边,在lct里,下标[1,n]是结点,下标[n+1,m+n]中的结点是边;边对应的结点才有权值(b)。
边按照a排序,并查集维护图的连通性。按a值从小到大不断地加边,维护并查集,
加一条边时,要同时在LCT里连接这条边的两个点u和v(先连接u和边对应的结点,再把边对应的结点和v相连)
会连成环,看LCT中u到v路径b最大的边 ,删掉,加入这条边。
ans=min{a+LCT中起点到终点最大的b}。
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN=150000+10;
const int MAXE=100000+10;
const int INF=0x3f3f3f3f;
struct Edge
{
int u,v,a,b;
} edges[MAXE];
int n,m,nCount=0,ans=INF;
int r[MAXN],ch[MAXN][2],fa[MAXN];//根,左右儿子,父节点
int b[MAXN],maxv[MAXN];//该点值(b)、该点对应区间的最大值对应结点的下标
bool rev[MAXN],isRoot[MAXN];翻转标记、根节点标记
int find(int x)//找x的根
{
if(r[x]==x) return x;
return r[x]=find(r[x]);
}
bool cmp(Edge x1,Edge x2)
{
return x1.a<x2.a;
}
/*....................Splay.....................*/
void update(int x)
{
if(!x) return;
swap(ch[x][0],ch[x][1]);
rev[x] ^= 1;
}
void pushdown(int x)
{
if(rev[x])
{
update(ch[x][0]);
update(ch[x][1]);
rev[x]=0;
}
}
void pushup(int x)
{
int lc=ch[x][0],rc=ch[x][1];
maxv[x]=x;
if(b[maxv[lc]]>b[maxv[x]])
maxv[x]=maxv[lc];
if(b[maxv[rc]]>b[maxv[x]])
maxv[x]=maxv[rc];
}
void rotate(int x)
{
int y=fa[x],tmp=ch[y][1]==x;
ch[y][tmp]=ch[x][!tmp];
fa[ch[y][tmp]]=y;
fa[x]=fa[y];
fa[y]=x;
ch[x][!tmp]=y;
if(isRoot[y])
{
isRoot[y]=0;
isRoot[x]=1;
}
else
ch[fa[x]][ch[fa[x]][1]==y]=x;
pushup(y);
}
void P(int x)//维护x和它的所有祖先
{
if(!isRoot[x])
P(fa[x]);
pushdown(x);
}
void splay(int x)
{
P(x);
while(!isRoot[x])
{
int f=fa[x],ff=fa[f];
if(isRoot[f])
rotate(x);
else
if((ch[ff][1]==f)==(ch[f][1]==x))
{
rotate(f);
rotate(x);
}
else
{
rotate(x);
rotate(x);
}
}
pushup(x);
}
/*...............Link Cut Tree..................*/
int access(int x)//x到根节点的preferred path
{
int y=0;
while(x)
{
splay(x);
isRoot[ch[x][1]]=1;
ch[x][1]=y;
isRoot[ch[x][1]]=0;
pushup(x);
y=x;
x=fa[x];
}
return y;
}
void makeroot(int x)//使x成为所在树的根
{
access(x);
splay(x);
update(x);
}
void link(int u,int v)
{
makeroot(u);
fa[u]=v;
}
void cut(int u,int v)
{
makeroot(u);
access(v);
splay(v);
fa[ch[v][0]]=fa[v];
fa[v]=0;
isRoot[ch[v][0]]=1;
ch[v][0]=0;
pushup(v);
}
/*................................................*/
int query(int x,int y)//求点x到y之间路径上b的最大值(返回该边)
{
makeroot(x);
access(y);
splay(y);
return maxv[y];
}
void solve(int i)//连成环,删
{
int u=edges[i].u,v=edges[i].v,w=edges[i].b;
int t=query(u,v);//找出b最大的边
if(w<b[t])
{
cut(edges[t-n].u,t);
cut(edges[t-n].v,t);
link(u,i+n);
link(v,i+n);
}
}
int main()
{
cin>>n>>m;
//初始化根为自身
for(int i=1;i<=n+m;i++)//将边也看成一个单独的节点
isRoot[i]=1;
for(int i=1;i<=n;i++)
r[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d%d",&edges[i].u,&edges[i].v,&edges[i].a,&edges[i].b);
sort(edges+1,edges+m+1,cmp);//按a大小排序
for(int i=1;i<=m;i++)
{
b[n+i]=edges[i].b;
maxv[n+i]=n+i;
}
for(int i=1;i<=m;i++)//维护连通,往LCT里面加边
{
int u=edges[i].u,v=edges[i].v,w=edges[i].b;
int rootu=find(u),rootv=find(v);
if(rootu!=rootv)//未形成环,加边
{
r[rootu]=rootv;
link(u,n+i);
link(v,n+i);
}
else//是环,处理
solve(i);
if(find(1)==find(n))//起点终点连通,出现解
ans=min(ans,b[query(1,n)]+edges[i].a);
}
if(ans==INF)
ans=-1;
cout<<ans<<endl;
return 0;
}
分析2
还有人用三分的方法做到了满分,这里就不说了。
代码2
#include <bits/stdc++.h>
using namespace std;
const int maxn=50000+5;
int cnt=0;
int n,m,i,j,a,b,best=100001;
struct nod{
int nex,a,b;
};
vector<nod> edges[maxn];
int dp[maxn],q[maxn];
bool vis[maxn];
int ans[maxn];
int cmp(nod x,nod y)
{
return x.a<y.a;
}
inline int spfa(int a)
{
if(ans[a]!=0) return ans[a];
memset(vis,0,sizeof(vis));
memset(dp,-1,sizeof(dp));
dp[1]=0;
int head,tail;
head=tail=1;
q[1]=1;
vis[1]=1;
int now,xia,b;
nod nex;
while(head<=tail)
{
now=q[head%50003];
vis[now]=0;
if(dp[n]!=-1 && dp[now]>=dp[n])
{
head++;
continue;
}
for(int j=0;j<edges[now].size();j++)
{
nex=edges[now][j];
xia=nex.nex;
if(nex.a>a) break;
b=max(dp[now],nex.b);
if(dp[xia]==-1 || b<dp[xia])
{
dp[xia]=b;
if(vis[xia]==0)
{
vis[xia]=1;
tail++;
q[tail%50003]=xia;
if(dp[q[(head+1)%50003]]>dp[q[tail%50003]])
{
swap(q[(head+1)%50003],q[tail%50003]);
}
}
}
}
head++;
}
if(dp[n]==-1) ans[a]=-1; else ans[a]=dp[n]+a;
if(dp[n]==-1) return -1;
return dp[n]+a;
}
int dfs(int l,int r)
{
cnt++;
if(cnt>1000) return 0;
if(l==r)
{
int temp=spfa(l);
if(temp!=-1) best=min(best,temp);
return 0;
}
if(l>r) return 0;
int mid=(l+r)/2;
int temp=spfa(mid);
if(temp==-1)
{
dfs(mid+1,min(r,best));
return 0;
}
best=min(best,temp);
int lef=best-(temp-mid);
if(spfa(l)-l!=spfa(mid)-mid) dfs(l,min(lef,mid));
else
{
int temp=spfa(l);
if(temp!=-1) best=min(best,temp);
}
if(spfa(r)-r!=spfa(mid)-mid) dfs(mid+1,min(r,best));
return 0;
}
int main()
{
int zuida=0;
memset(ans,0,sizeof(ans));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) edges[i].clear();
nod temp;
for(int k=1;k<=m;k++)
{
scanf("%d%d%d%d",&i,&j,&b,&a);
if(i==j) continue;
zuida=max(zuida,a);
temp.nex=j; temp.a=a; temp.b=b;
edges[i].push_back(temp);
temp.nex=i;
edges[j].push_back(temp);
}
for(int i=1;i<=n;i++)
{
sort(edges[i].begin(),edges[i].end(),cmp);
}
if(spfa(50000)==-1)
{
cout<<-1<<endl;
return 0;
}
dfs(1,zuida);
cout<<best<<endl;
}
动物园
(传送门)
题意
长度为N的字符串,Num[i]表示以i结尾的后缀与字符串前缀的最长公共长度,并且两个串不重叠。求(Num[i]+1)的乘积
分析
裸的KMP,求出next数组,顺便求出cnt数组(长度为i的前缀经过几次fix=next[fix]会得到0)
重新匹配一次,这次注意当fix*2>i的时候令fix=next[fix]即可
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000000+10;
const int MOD=1000000007;
char s[maxn];
int next[maxn],cnt[maxn];
long long ans;
void getnext()
{
int fix=0;
cnt[1]=1;
for(int i=2;s[i];i++)
{
while(fix && s[fix+1]!=s[i]) fix=next[fix];
if(s[fix+1]==s[i]) fix++;
next[i]=fix;
cnt[i]=cnt[fix]+1;
}
}
void kmp()
{
ans=1;
int fix=0;
for(int i=2;s[i];i++)
{
while(fix && s[fix+1]!=s[i]) fix=next[fix];
if(s[fix+1]==s[i]) fix++;
while(fix*2>i) fix=next[fix];
ans*=(cnt[fix]+1);
ans%=MOD;
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%s",s+1);
getnext();
kmp();
printf("%d\n",ans);
}
return 0;
}
随机数生成器
(传送门)
题意
通过给定参数得到N*M的随机数矩阵(只包含数1-N*M),求从左上角到右下角得到数列升序排序后字典序最小的路径
分析
之所以能贪心,是因为有1的时候必选1,而2,3,4同理,所以从小到大开始加数,并暴力删除由于选这个数而不可选的其他数,并得到答案,因为每个数只会被删除一次,所以注意删除的方式可以大大降低复杂度。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5000+5;
long long seed,a,b,c,MOD;
int arr[MAXN*MAXN],mp[MAXN][MAXN];
bool vis[MAXN][MAXN];
int m,n,Q;
int getx() { return seed=(a*seed*seed%MOD+b*seed%MOD+c)%MOD; }
int main()
{
scanf("%lld%lld%lld%lld%lld%d%d%d",&seed,&a,&b,&c,&MOD,&m,&n,&Q);
for(int i=1;i<=m*n;i++)
{
arr[i]=i;
swap(arr[i],arr[getx()%i+1]);
}
while(Q--)
{
int x,y;
scanf("%d%d",&x,&y);
swap(arr[x],arr[y]);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
mp[i][j]=arr[(i-1)*n+j];
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
arr[mp[i][j]]=(i-1)*n+j;
for(int i=1;i<=m*n;i++)
{
int x=arr[i]/n+1-(arr[i]%n==0),y=arr[i]-(x-1)*n;
if(!vis[x][y])
{
if(i!=1) printf(" ");
printf("%d",i);
for(int j=x+1;j<=m;j++)
for(int k=y-1;k>0;k--)
{
if(vis[j][k]) break;
vis[j][k]=1;
}
for(int j=x-1;j>0;j--)
for(int k=y+1;k<=n;k++)
{
if(vis[j][k]) break;
vis[j][k]=1;
}
}
}
printf("\n");
return 0;
}
购票
(传送门)
题意
有根树(1为根),边有长度。每个点u有三个属性(len[u],p[u],q[u]),每次u可以转移到u的某个祖先节点v(v满足dist(u,v)<=len[u]),代价为p[u]*dist(u,v)+q[u]。求每个点都转移到1的代价。
分析
网上的解法很多,官方题解给的是树分治+CDQ分治,下面给出的是考虑用树链剖分+线段树+三分来做时间复杂度O(n(logn)^3),应该是比较好写的一个了。
代码
#include <bits/stdc++.h>
using namespace std;
#define lson (c<<1)
#define rson (c<<1|1)
#define mid ((l+r)>>1)
const long long INF=999999999999999999LL;
const int MAXN=200000+10;
const int MAXM=MAXN<<2;
struct point
{
long long x,y;
int num;
point(){}
point(long long _x,long long _y){ x=_x,y=_y;}
friend point operator -(point a,point b){ return point(a.x-b.x,a.y-b.y);}
friend double operator /(point a,point b){ return ((0.0+a.x)*b.y-(0.0+a.y)*b.x);}
}p;
//graph
int tot,head[MAXN];
struct Edge
{
int u,next;
long long v;
} edges[MAXN];
inline void addEdge(int x,int u,long long v)
{
edges[++tot]=(Edge){u,head[x],v};
head[x]=tot;
}
//segment tree
int ch[MAXM][2];
vector<point> hu[MAXM];
//tree
int n,F[MAXN],g[MAXN],H[MAXN],size[MAXN],son[MAXN],T,Que[MAXN],top[MAXN],ll,rr,xx;
long long s[MAXN],P[MAXN],Q[MAXN],lim[MAXN],dis[MAXN],f[MAXN];
void change(int c,int l,int r,int x)
{
int s;
while(((s=hu[c].size())>1) && ((p-hu[c][s-2])/(hu[c][s-1]-hu[c][s-2])>0))
hu[c].pop_back();
hu[c].push_back(p);
if(l>=r) return;
if(x<=mid) change(lson,l,mid,x);
else change(rson,mid+1,r,x);
}
long long query(int c,int l,int r)
{
long long ans=INF;
int lt=0,rt=hu[c].size()-1,m1,m2,i;
long long lv,rv;
if(ll<=l&&r<=rr)
{
for(lt=0,rt=hu[c].size()-1;rt-lt>3;)
{
m1=lt+(rt-lt)/3;
m2=rt-(rt-lt)/3;
lv=P[xx]*(dis[xx]-hu[c][m1].x)+hu[c][m1].y;
rv=P[xx]*(dis[xx]-hu[c][m2].x)+hu[c][m2].y;
if(lv<=rv) rt=m2;
else lt=m1;
}
for(int i=lt;i<=rt;i++)
ans=min(ans,P[xx]*(dis[xx]-hu[c][i].x)+Q[xx]+hu[c][i].y);
return ans;
}
if(ll<=mid) ans=query(lson,l,mid);
if(rr>mid) ans=min(ans,query(rson,mid+1,r));
return ans;
}
void dfs1(int c)
{
int t=0;
size[c]=1;
for(int i=head[c];i;i=edges[i].next)
{
dis[edges[i].u]=dis[c]+edges[i].v;
dfs1(edges[i].u);
size[c]+=size[edges[i].u];
if(size[edges[i].u]>t)
{
t=size[edges[i].u];
son[c]=edges[i].u;
}
}
}
void dfs2(int c,int tp)
{
g[c]=++T;
H[T]=c;
top[c]=tp;
if(!son[c]) return;
dfs2(son[c],tp);
for(int i=head[c];i;i=edges[i].next)
if(edges[i].u!=son[c])
dfs2(edges[i].u,edges[i].u);
}
void cal(int l,int r,int c)
{
long long ans=INF;
for(xx=c;dis[top[r]]>dis[l];r=F[top[r]])
{
long long=g[top[r]],rr=g[r];
ans=min(ans,query(1,1,n));
}
ll=g[l],rr=g[r];
ans=min(ans,query(1,1,n));
f[c]=ans;
p=point(dis[c],f[c]); p.num=c;
change(1,1,n,g[c]);
}
int main()
{
int h,t,c,l,r;
long long far;
scanf("%d%d",&n,&t);
for(int i=2;i<=n;i++)
scanf("%d%lld%lld%lld%lld",F+i,s+i,P+i,Q+i,lim+i),addEdge(F[i],i,s[i]);
dfs1(1);
dfs2(1,1);
Que[h=t=1]=1;
while(h<=t)
{
c=Que[h++];
for(int i=head[c];i;i=edges[i].next)
Que[++t]=edges[i].u;
}
p=point(0,0); p.num=1;
change(1,1,n,1);
for(int i=2;i<=n;i++)
{
t=c=Que[i];
far=dis[c]-lim[c];
for(t=F[t];t>1&&dis[F[top[t]]]>far;t=F[top[t]]);
if(t>1)
{
l=g[top[t]]; r=g[t];
while(l<=r)
{
if(dis[H[mid]]>=far)
{
t=H[mid];
r=mid-1;
}
else l=mid+1;
}
}
else t=1;
cal(t,F[c],c);
}
for(int i=2;i<=n;i++)
printf("%lld\n",f[i]);
return 0;
}