标签:zjoi 维表 顺序 namespace 写法 expand 操作 tchar 遇到
萌新刚学Splay,被这题卡了好久。
写一写自己死去的经过。
没看清题中对于稳定排序的描述而误入歧途,想直接在Splay中查找min,后来发现这种做法是错的。
rotate写错了233
后来改用直接在数组中记录对应的点在Splay中位置的做法,由于懒采用了翻转一次删一个点的做法。
因为想到如果将目标节点Splay到根后它的左儿子都是序列中在它左边的点,所以直接修改了左边。
后来发现这种方法会导致哨兵也被翻转而导致TLE。
由于直接将目标节点Splay到根,导致没有了find的过程,由于reverse标记的正确性需要由从上往下的pushdown来得到,所以没有pushdown肯定是错的。写了一个递归的手动Pushdown。
不知道是否有锅,删除的方法调不对,按理来说应该是可行的。
最后采用了不删除的写法,但是由于直接查找next又出了锅。改成查找排名后正确。
死了一下午后终于AC。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=101000;
const int inf=2147483647;
struct value{
int val,pos;
bool operator<(const value&b)const {
return val^b.val?val<b.val:pos<b.pos;
}
}a[N];
int root,tot,qwq;
int sx[N],nodeInTree[N];
struct node{
int father,size,son[2],reverse,val;
}t[N];
inline void update(int x){
t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+1;
}
inline void pushdown(int x){
if(t[x].reverse){
t[t[x].son[0]].reverse^=1;
t[t[x].son[1]].reverse^=1;
swap(t[x].son[0],t[x].son[1]);
t[x].reverse=0;
}
}
inline int get(int x){
return t[t[x].father].son[1]==x;
}
inline void rotate(int x){
int y=t[x].father;
int z=t[y].father;
pushdown(y),pushdown(x);
int k=get(x);
t[z].son[get(y)]=x;
t[x].father=z;
t[y].son[k]=t[x].son[k^1];
t[t[y].son[k]].father=y;
t[x].son[k^1]=y;
t[y].father=x;
update(y),update(x);
}
inline void splay(int x,int k){
while(t[x].father!=k){
int y=t[x].father;
int z=t[y].father;
if(z^k)
rotate(get(x)^get(y)?x:y);
rotate(x);
}
if(!k)root=x;
}
inline void Pushdown(int x){
if(!x)return ;
Pushdown(t[x].father);
pushdown(x);
}
inline int build(int father,int l,int r){
if(l>r)return 0;
int mid=(l+r)>>1;
int x=++tot;
if(mid==0)qwq=x;
t[x].father=father;
nodeInTree[sx[mid]]=x;
t[x].val=mid;
t[x].son[0]=build(x,l,mid-1);
t[x].son[1]=build(x,mid+1,r);
update(x);
return x;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline int next(int x,int k){
Pushdown(x);
splay(x,0);
int p=root;
pushdown(p);
p=t[p].son[k];
while(t[p].son[k^1])
pushdown(p),p=t[p].son[k^1];
return p;
}
inline void show(int x,int father){
if(!x)return ;
pushdown(x);
printf("%d %d\n",t[father].val,t[x].val);
show(t[x].son[0],x);
show(t[x].son[1],x);
}
inline void debug(){
t[0].val=233;
putchar('\n');
show(root,0);
putchar('\n');
}
inline int find(int k){
int x=root;
while(1){
pushdown(x);
if(k<=t[t[x].son[0]].size)x=t[x].son[0];
else if(k<=t[t[x].son[0]].size+1)return x;
else k-=t[t[x].son[0]].size+1,x=t[x].son[1];
}
}
int QAQ(){
//freopen("qaq.in","r",stdin);
int n=read();
for(int i=1;i<=n;++i)
a[i].val=read(),a[i].pos=i;
sort(a+1,a+n+1);
for(int i=1;i<=n;++i)
sx[a[i].pos]=i;
root=build(0,0,n+1);
for(int i=1;i<=n;++i){
Pushdown(nodeInTree[i]);
splay(nodeInTree[i],0);
int ans=t[t[root].son[0]].size;
printf("%d ",ans);
int pre=find(i);
int next=find(ans+2);
splay(pre,0);
splay(next,pre);
int x=t[root].son[1];
t[t[x].son[0]].reverse^=1;
}
return false;
}
}
int main(){
return orz::QAQ();
}
动态逆序对可以用树套树等神奇的数据结构来做,但是蒟蒻太菜了都不会。
我们来考虑CDQ分治,我们倒着来考虑删除的过程,也就变成了询问加入每一个数会产生多少的新的逆序对。
我们设每一个数的加入时间为\(t_i\),则我们要统计的是\(pos_j<pos_i,val_j>val_i,t_j<=t_i或pos_j>pos_i,val_j<val_i,t_j<=t_i\)的个数,这就是一个三维偏序的板子。
因为我懒,所以我将pos数组反序,val数组取反后又跑了一遍原来的CDQ。
如果我们直接顺带用CDQ统计未加数前的逆序对的话,我们会发现每一个逆序对被统计了两次,除2即可,而后加的数因为有时间限制没有这个问题,最后统计前缀和输出答案。
一开始把删某个数写成了删某个位置的数,导致了迷之错误。
逆序对个数是\(n^2\)级别的,所以这一题需要开long long。
#include<cstdio>
using namespace std;
namespace orz{
const int N=110000;
int k;
int c[N];
int pos[N],val[N],t[N],ans[N],p[N];
int q[N];
long long d[N];
long long sum[N];
int posInArray[N];
inline void add(int x,int y){
for(;x<=k;x+=x&-x)
c[x]+=y;
}
inline int ask(int x){
int ans=0;
for(;x;x-=x&-x)
ans+=c[x];
return ans;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
void cdq(int l,int r){
if(l==r)return ;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
int tl=l,tr=mid+1;
for(int i=l;i<=r;++i){
if((tl<=mid&&val[pos[tl]]>val[pos[tr]])||tr>r)
add(t[pos[tl]],1),q[i]=pos[tl++];
else ans[pos[tr]]+=ask(t[pos[tr]]),q[i]=pos[tr++];
}
for(int i=l;i<=mid;++i)add(t[pos[i]],-1);
for(int i=l;i<=r;++i)pos[i]=q[i];
}
int QAQ(){
int n,m;
n=read(),m=read();
k=m+4;
for(int i=1;i<=n;++i)
val[i]=read(),pos[i]=i;
for(int i=1;i<=n;++i)
posInArray[val[i]]=i;
for(int i=1;i<=m;++i)
t[posInArray[read()]]=m-i+2;
for(int i=1;i<=n;++i)
if(!t[i])
t[i]=1;
cdq(1,n);
for(int i=1;i<=n;++i)
pos[i]=n-i+1,val[i]=-val[i];
cdq(1,n);
for(int i=1;i<=n;++i)
d[t[pos[i]]]+=ans[pos[i]];
d[1]>>=1;
for(int i=1;i<=m+1;++i)
sum[i]=sum[i-1]+d[i];
for(int i=m+1;i>1;--i)
printf("%lld\n",sum[i]);
return false;
}
}
int main(){
return orz::QAQ();
}
学分治的时候顺带学的,现在才知道这个叫线段树分治。
具体应用是对于求解答案有很多的重复操作,我们不进行其中某一个操作,而且操作可以实现回退,那我们可以对操作分治,先操作右区间,递归左区间,回退操作,操作左区间,递归右区间,回退操作。
这样一波操作后我们发现当我们到达一个叶子节点时,所有其他区间的操作都已经完成,于是我们就获得了很好的复杂度。
对于这一题,我们用一个并查集,因为要回退,所以我们采用按秩合并。
我们链接左边没有右边有的边,另一侧同理,最后到达叶子时只剩这个询问的边没有链接,输出答案即可。
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
namespace orz{
const int N=210000;
int n,m,k;
struct edge{
int x,y;
}a[N];
struct change{
int f,pos,val;
change(int t,int a,int b){
f=t;pos=a;val=b;
}
};
int f[N];
int size[N];
int c[N][5];
bool vis[N];
inline int get(int x){
if(f[x]==x)return x;
return get(f[x]);
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
void solve(int l,int r){
if(l==r){
printf(size[get(1)]==n?"Connected\n":"Disconnected\n");
return ;
}
int mid=(l+r)>>1;
int x,y;
stack<change>s;
for(int i=l;i<=mid;++i)
for(int j=1;j<=c[i][0];++j)
vis[c[i][j]]=true;
for(int i=mid+1;i<=r;++i)
for(int j=1;j<=c[i][0];++j){
if(!vis[c[i][j]]){
x=get(a[c[i][j]].x);
y=get(a[c[i][j]].y);
if(x==y)continue;
if(size[x]<size[y]){
s.push(change(0,x,f[x]));
f[x]=y;
s.push(change(1,y,size[y]));
size[y]+=size[x];
}
else{
s.push(change(0,y,f[y]));
f[y]=x;
s.push(change(1,x,size[x]));
size[x]+=size[y];
}
}
}
for(int i=l;i<=mid;++i)
for(int j=1;j<=c[i][0];++j)
vis[c[i][j]]=false;
solve(l,mid);
while(s.size()){
switch(s.top().f){
case 0:
f[s.top().pos]=s.top().val;
break;
case 1:
size[s.top().pos]=s.top().val;
break;
}
s.pop();
}
for(int i=mid+1;i<=r;++i)
for(int j=1;j<=c[i][0];++j)
vis[c[i][j]]=true;
for(int i=l;i<=mid;++i)
for(int j=1;j<=c[i][0];++j){
if(!vis[c[i][j]]){
x=get(a[c[i][j]].x);
y=get(a[c[i][j]].y);
if(x==y)continue;
if(size[x]<size[y]){
s.push(change(0,x,f[x]));
f[x]=y;
s.push(change(1,y,size[y]));
size[y]+=size[x];
}
else{
s.push(change(0,y,f[y]));
f[y]=x;
s.push(change(1,x,size[x]));
size[x]+=size[y];
}
}
}
for(int i=mid+1;i<=r;++i)
for(int j=1;j<=c[i][0];++j)
vis[c[i][j]]=false;
solve(mid+1,r);
while(s.size()){
switch(s.top().f){
case 0:
f[s.top().pos]=s.top().val;
break;
case 1:
size[s.top().pos]=s.top().val;
break;
}
s.pop();
}
}
int QAQ(){
n=read(),m=read();
for(int i=1;i<=n;++i)
f[i]=i,size[i]=1;
for(int i=1;i<=m;++i)
a[i].x=read(),a[i].y=read();
k=read();
for(int i=1;i<=k;++i){
c[i][0]=read();
for(int j=1;j<=c[i][0];++j)
vis[c[i][j]=read()]=true;
}
for(int i=1;i<=m;++i)
if(!vis[i]){
int x=get(a[i].x);
int y=get(a[i].y);
if(x==y)continue;
if(size[x]<size[y]){
f[x]=y;
size[y]+=size[x];
}
else{
f[y]=x;
size[x]+=size[y];
}
}
for(int i=1;i<=m;++i)
vis[i]=false;
solve(1,k);
return false;
}
}
int main(){
return orz::QAQ();
}
LCT裸题。
为每一个颜色维护一个LCT,在splay中维护max信息。
问题在于判断修改的合法性,如果一条边直接加入是不合法的,不一定这个操作是不合法的,有可能对同一条边修改为与之前相同的颜色,这样可能会误判,所以我们先删边再加边,这样可以解决这个问题。
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
namespace orz{
const int N=11000,C=10,M=111000;
struct node{
int father,son[2],val,max,reverse;
}t[N*C];
struct edge{
int x,y,id;
bool operator<(const edge &b)const {
return this->x^b.x?this->x<b.x:this->y<b.y;
}
edge(int a,int b,int d){
x=min(a,b),y=max(a,b);
id=d;
}
};
set<edge>s;
int color[M];
int degree[C][N];
inline int get(int x){
return t[t[x].father].son[1]==x;
}
inline bool notroot(int x){
return t[t[x].father].son[0]==x||t[t[x].father].son[1]==x;
}
inline void update(int x){
t[x].max=max(t[x].val,max(t[t[x].son[0]].max,t[t[x].son[1]].max));
}
inline void pushdown(int x){
if(t[x].reverse){
t[t[x].son[0]].reverse^=1;
t[t[x].son[1]].reverse^=1;
swap(t[x].son[0],t[x].son[1]);
t[x].reverse=0;
}
}
void pushall(int x){
if(notroot(x))
pushall(t[x].father);
pushdown(x);
}
inline void rotate(int x){
int y=t[x].father;
int z=t[y].father;
int k=get(x);
if(notroot(y))t[z].son[get(y)]=x;
t[x].father=z;
t[y].son[k]=t[x].son[k^1];
t[t[y].son[k]].father=y;
t[x].son[k^1]=y;
t[y].father=x;
update(y);
}
inline void splay(int x){
pushall(x);
int y;
while(notroot(x)){
y=t[x].father;
if(notroot(y))
rotate(get(x)^get(y)?x:y);
rotate(x);
}
update(x);
}
inline void access(int x){
for(int y=0;x;x=t[y=x].father)
splay(x),t[x].son[1]=y,update(x);
}
inline void makeroot(int x){
access(x);
splay(x);
t[x].reverse^=1;
}
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
inline void link(int x,int y){
makeroot(x);
t[x].father=y;
}
inline void cut(int x,int y){
makeroot(x);
access(y);
splay(x);
t[y].father=t[x].son[1]=0;
}
inline int ask(int x,int y){
split(x,y);
return t[y].max;
}
inline int findroot(int x){
access(x);
splay(x);
while(t[x].son[0])
x=t[x].son[0];
return x;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
int n,m,c,k;
int x,y,z;
int id;
n=read(),m=read(),c=read(),k=read();
for(int i=1;i<=n;++i){
x=read();
for(int j=0;j<c;++j)
t[i+j*n].val=x;
}
for(int i=1;i<=m;++i){
x=read(),y=read(),z=read();
++degree[z][x];
++degree[z][y];
s.insert(edge(x,y,i));
color[i]=z;
link(x+z*n,y+z*n);
}
while(k--){
switch(read()){
case 0:
x=read(),y=read();
for(int i=0;i<c;++i){
access(x+i*n);
splay(x+i*n);
t[x+i*n].val=y;
update(x+i*n);
}
break;
case 1:
x=read(),y=read(),z=read();
if(s.find(edge(x,y,0))==s.end()){
printf("No such edge.\n");
break;
}
id=s.find(edge(x,y,0))->id;
--degree[color[id]][x];
--degree[color[id]][y];
if(degree[z][x]>=2||degree[z][y]>=2){
printf("Error 1.\n");
++degree[color[id]][x];
++degree[color[id]][y];
break;
}
cut(x+color[id]*n,y+color[id]*n);
if(findroot(x+z*n)==findroot(y+z*n)){
printf("Error 2.\n");
++degree[color[id]][x];
++degree[color[id]][y];
link(x+color[id]*n,y+color[id]*n);
break;
}
printf("Success.\n");
link(x+z*n,y+z*n);
color[id]=z;
++degree[z][x];
++degree[z][y];
break;
case 2:
z=read();x=read(),y=read();
if(findroot(x+z*n)!=findroot(y+z*n)){
printf("-1\n");
}
else {
printf("%d\n",ask(x+z*n,y+z*n));
}
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
这破题调了两个小时QAQ
就是线段树模版2的LCT版本,注意两个标记的下传顺序。
这题的模数是51061,看起来好像不会爆int的样子,结果不开longlong只有5分。
\(51061*51061=2607225721>2147483647\)
所以这个题就爆int了。
用了两种写法,一种我的写法是借用0号节点来pushdown,DKY的写法是把pushdown分开。
相比起来我的写法太菜了。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=110000;
const int MOD=51061;
struct node{
int reverse,father,size,son[2];
long long val,add,sum,mul;
}t[N];
int n;
inline int get(int x){
return t[t[x].father].son[1]==x;
}
inline bool notroot(int x){
return t[t[x].father].son[0]==x||t[t[x].father].son[1]==x;
}
inline void update(int x){
t[x].sum=(t[t[x].son[0]].sum+t[t[x].son[1]].sum+t[x].val)%MOD;
t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+1;
}
inline void pushmul(int x,long long mul){
t[x].add=(t[x].add*mul)%MOD;
t[x].val=(t[x].val*mul)%MOD;
t[x].sum=(t[x].sum*mul)%MOD;
t[x].mul=(t[x].mul*mul)%MOD;
}
inline void pushadd(int x,long long add){
t[x].add=(t[x].add+add)%MOD;
t[x].val=(t[x].val+add)%MOD;
t[x].sum=(t[x].sum+add*t[x].size)%MOD;
}
inline void pushdown(int x){
if(t[x].mul^1){
pushmul(t[x].son[0],t[x].mul);
pushmul(t[x].son[1],t[x].mul);
t[x].mul=1;
}
if(t[x].add){
pushadd(t[x].son[0],t[x].add);
pushadd(t[x].son[1],t[x].add);
t[x].add=0;
}
if(t[x].reverse){
t[t[x].son[0]].reverse^=1;
t[t[x].son[1]].reverse^=1;
swap(t[x].son[0],t[x].son[1]);
t[x].reverse=0;
}
}
void pushall(int x){
if(notroot(x))pushall(t[x].father);
pushdown(x);
}
inline void rotate(int x){
int y=t[x].father,z=t[y].father;
int k=get(x);
if(notroot(y))t[z].son[get(y)]=x;
t[x].father=z;
t[y].son[k]=t[x].son[k^1];
t[t[y].son[k]].father=y;
t[x].son[k^1]=y;
t[y].father=x;
update(y);update(x);
}
inline void splay(int x){
pushall(x);
int y;
while(notroot(x)){
y=t[x].father;
if(notroot(y))
rotate(get(x)^get(y)?x:y);
rotate(x);
}
update(x);
}
inline void access(int x){
for(int y=0;x;x=t[y=x].father)
splay(x),t[x].son[1]=y,update(x);
}
inline void makeroot(int x){
access(x);
splay(x);
t[x].reverse^=1;
}
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
inline void cut(int x,int y){
makeroot(x);
access(y);
splay(x);
t[y].father=t[x].son[1]=0;
update(x);
}
inline void link(int x,int y){
makeroot(x);
t[x].father=y;
}
inline long long read(){
long long a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
char s[4];
int QAQ(){
n=read();
int q=read();
int x,y;
long long z;
for(int i=1;i<=n;++i)
t[i].size=t[i].mul=t[i].val=t[i].sum=1;
for(int i=1;i<n;++i){
x=read(),y=read();
link(x,y);
}
while(q--){
scanf("%s",s);
switch(s[0]){
case '+':
x=read(),y=read(),z=read();
split(x,y);
pushadd(y,z);
break;
case '-':
cut(read(),read());
link(read(),read());
break;
case '*':
x=read(),y=read(),z=read();
split(x,y);
pushmul(y,z);
break;
case '/':
x=read(),y=read();
split(x,y);
printf("%lld\n",t[y].sum);
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=110000;
const int MOD=51061;
struct node{
int reverse,father,size,son[2];
long long val,add,sum,mul;
}t[N];
int n;
inline int get(int x){
return t[t[x].father].son[1]==x;
}
inline bool notroot(int x){
return t[t[x].father].son[0]==x||t[t[x].father].son[1]==x;
}
inline void update(int x){
t[x].sum=(t[t[x].son[0]].sum+t[t[x].son[1]].sum+t[x].val)%MOD;
t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+1;
}
inline void pushdown(int x){
if(t[x].reverse){
t[t[x].son[0]].reverse^=1;
t[t[x].son[1]].reverse^=1;
swap(t[x].son[0],t[x].son[1]);
t[x].reverse=0;
}
if(t[x].mul^1){
t[t[x].son[0]].add=(t[t[x].son[0]].add*t[x].mul)%MOD;
t[t[x].son[1]].add=(t[t[x].son[1]].add*t[x].mul)%MOD;
t[t[x].son[0]].mul=(t[t[x].son[0]].mul*t[x].mul)%MOD;
t[t[x].son[1]].mul=(t[t[x].son[1]].mul*t[x].mul)%MOD;
t[t[x].son[0]].sum=(t[t[x].son[0]].sum*t[x].mul)%MOD;
t[t[x].son[1]].sum=(t[t[x].son[1]].sum*t[x].mul)%MOD;
t[t[x].son[0]].val=(t[t[x].son[0]].val*t[x].mul)%MOD;
t[t[x].son[1]].val=(t[t[x].son[1]].val*t[x].mul)%MOD;
t[x].mul=1;
}
if(t[x].add){
t[t[x].son[0]].add=(t[t[x].son[0]].add+t[x].add)%MOD;
t[t[x].son[1]].add=(t[t[x].son[1]].add+t[x].add)%MOD;
t[t[x].son[0]].sum=(t[t[x].son[0]].sum+t[x].add*t[t[x].son[0]].size)%MOD;
t[t[x].son[1]].sum=(t[t[x].son[1]].sum+t[x].add*t[t[x].son[1]].size)%MOD;
t[t[x].son[0]].val=(t[t[x].son[0]].val+t[x].add)%MOD;
t[t[x].son[1]].val=(t[t[x].son[1]].val+t[x].add)%MOD;
t[x].add=0;
}
}
void pushall(int x){
if(notroot(x))pushall(t[x].father);
pushdown(x);
}
inline void rotate(int x){
int y=t[x].father,z=t[y].father;
int k=get(x);
if(notroot(y))t[z].son[get(y)]=x;
t[x].father=z;
t[y].son[k]=t[x].son[k^1];
t[t[y].son[k]].father=y;
t[x].son[k^1]=y;
t[y].father=x;
update(y);
}
inline void splay(int x){
pushall(x);
int y;
while(notroot(x)){
y=t[x].father;
if(notroot(y))
rotate(get(x)^get(y)?x:y);
rotate(x);
}
update(x);
}
inline void access(int x){
for(int y=0;x;x=t[y=x].father)
splay(x),t[x].son[1]=y,update(x);
}
inline void makeroot(int x){
access(x);
splay(x);
t[x].reverse^=1;
}
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
inline void cut(int x,int y){
makeroot(x);
access(y);
splay(x);
t[y].father=t[x].son[1]=0;
update(x);
}
inline void link(int x,int y){
makeroot(x);
t[x].father=y;
}
inline void add(int x,int y,int k){
split(x,y);
t[0]=t[n+2];
t[0].son[0]=y;
t[0].add=k;
pushdown(0);
t[0]=t[n+2];
}
inline void mul(int x,int y,int k){
split(x,y);
t[0]=t[n+2];
t[0].son[0]=y;
t[0].mul=k;
pushdown(0);
t[0]=t[n+2];
}
inline long long ask(int x,int y){
split(x,y);
return t[y].sum;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
char s[4];
int QAQ(){
n=read();
int q=read();
int x,y;
for(int i=1;i<=n;++i)
t[i].mul=t[i].val=t[i].sum=1;
t[n+2].add=t[n+2].sum=t[n+2].val=t[n+2].reverse=0;
t[n+2].size=0;
t[n+2].mul=1;
t[n+2].son[0]=t[n+2].son[1]=n+1;
for(int i=1;i<n;++i){
x=read(),y=read();
link(x,y);
}
while(q--){
scanf("%s",s);
switch(s[0]){
case '+':
x=read(),y=read();
add(x,y,read());
break;
case '-':
cut(read(),read());
link(read(),read());
break;
case '*':
x=read(),y=read();
mul(x,y,read());
break;
case '/':
printf("%lld\n",ask(read(),read()));
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
这道题看起来十分像LIS,实际上就是一个LIS。
这题的DP方程相比普通LIS多了一些限制条件。
DP方程:
\(f_i=max(f_k)+1\),\(k\)<\(j\),\(a_k\leq Min_i\),\(Max_k\leq a_i\)
这看起来就像是个三维偏序,但是又有所不同,主要区别在于这一题中的关系是在不同的数组之间的。
我们第一维是位置,原来就排好了序。
我们归并第二位,我们先将左区间的数按a排序,右区间的数按min排序,这样我们就满足了第二个限制条件。
我们将对应的f值插入到Max的位置上,查询的时候查询a。
这样我们就神奇的用CQD分治解决了这一题。
但是我们传统CDQ为了方便归并,采用的是先递归左右区间在处理本区间的顺序,这样的顺序显然与我们的dp顺序不同,所以我们采用递归左区间,处理本区间,递归右区间的顺序。因为本来就没法归并,所以用sort就好了。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=100000;
int a[N],c[N];
int tpos[N];
int Min[N],Max[N];
int f[N];
int n;
struct node{
int pos,val;
bool operator<(const node &b)const {
return this->val^b.val?this->val<b.val:this->pos<b.pos;
}
}ta[N],tmin[N];
inline void add(int x,int k){
for(;x<=n;x+=x&-x)
c[x]=max(c[x],k);
}
inline void remove(int x){
for(;x<=n;x+=x&-x)
c[x]=0;
}
inline int ask(int x){
int ans=0;
for(;x;x-=x&-x)
ans=max(ans,c[x]);
return ans;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void cdq(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq(l,mid);
for(int i=l;i<=mid;++i)
ta[i].val=a[i],ta[i].pos=i;
for(int i=mid+1;i<=r;++i)
tmin[i].val=Min[i],tmin[i].pos=i;
sort(ta+l,ta+mid+1);
sort(tmin+mid+1,tmin+r+1);
int tl=l,tr=mid+1;
for(int i=l;i<=r;++i){
if((ta[tl].val<=tmin[tr].val&&tl<=mid)||tr>r)
add(Max[ta[tl].pos],f[ta[tl].pos]),++tl;
else
f[tmin[tr].pos]=max(ask(a[tmin[tr].pos])+1,f[tmin[tr].pos]),++tr;
}
for(int i=l;i<=mid;++i)
remove(Max[ta[i].pos]);
cdq(mid+1,r);
}
int QAQ(){
n=read();
int m=read();
int x,y;
for(int i=1;i<=n;++i)
a[i]=Max[i]=Min[i]=read();
for(int i=1;i<=m;++i){
x=read(),y=read();
Max[x]=max(Max[x],y);
Min[x]=min(Min[x],y);
}
for(int i=1;i<=n;++i)
f[i]=1;
cdq(1,n);
int ans=-1000;
for(int i=1;i<=n;++i)
ans=max(ans,f[i]);
printf("%d\n",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
我们先看一个前置的题:P2397
注意这个题的内存限制是5m,一个数组也开不下。
我们注意到这个题目的特点是它的众数数目一定大于所有数的一半。
我们记录一个当前数和当前数的个数,遇到一个不同的数个数减一,遇到一个相同的数个数加一,如果个数为零了改变这个数。扫完整个序列后存的数就是答案。
这个证明显然,应用鸽巢原理的想法,就算所有数都与众数相消也消不完,所以最后的答案一定是正确的。
我们再回到这个题
我们会发现这种做法是与顺序无关的,所以我们用一个线段树来维护,线段树每个节点记录这个区间处理完的众数和个数,合并时如果两边的众数相同把个数相加,不同的话取数目多的众数,再用多的减少的,我们可以得出如此处理和原来的处理是等价的,我们维护的信息满足可并性。
但是我们查到的值只是可能成为答案,我们还需要check一下,我们可以建500000棵值域平衡树,前驱后继相减得到区间内某一个数的具体大小。
因为我们写了一棵线段树,一棵平衡树,所以这个题变得异常难写。
#include<cstdio>
#include<algorithm>
#include<cstdlib>
using namespace std;
namespace orz{
const int N=600000;
const int inf=2147483647;
int n,m;
int color[N];
int root[N];
int c[N];
struct segmentTree{
int val,cnt;
}st[N<<2];
struct Treap{
int son[2],size,val,dat;
}t[N*6];
int tot=1;
inline segmentTree merge(const segmentTree &x,const segmentTree &y){
segmentTree res;
if(x.val==y.val){
res.val=x.val;
res.cnt=x.cnt+y.cnt;
}
else {
if(x.cnt>y.cnt){
res.val=x.val;
res.cnt=x.cnt-y.cnt;
}
else {
res.val=y.val;
res.cnt=y.cnt-x.cnt;
}
}
return res;
}
void build(int p,int l,int r){
if(l==r){
st[p].val=color[l];st[p].cnt=1;
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
st[p]=merge(st[p<<1],st[p<<1|1]);
}
inline segmentTree query(int p,int l,int r,int L,int R){
if(l==L&&r==R)return st[p];
int mid=(L+R)>>1;
if(r<=mid)return query(p<<1,l,r,L,mid);
else if(l>mid)return query(p<<1|1,l,r,mid+1,R);
else return merge(query(p<<1,l,mid,L,mid),query(p<<1|1,mid+1,r,mid+1,R));
}
inline void change(int p,int k,int q,int L,int R){
if(L==R){
st[p].val=k;
st[p].cnt=1;
return;
}
int mid=(L+R)>>1;
if(q<=mid)change(p<<1,k,q,L,mid);
else change(p<<1|1,k,q,mid+1,R);
st[p]=merge(st[p<<1],st[p<<1|1]);
}
inline int New(int val){
t[++tot].val=val;
t[tot].dat=rand();
t[tot].size=1;
return tot;
}
inline void update(int p){
t[p].size=t[t[p].son[0]].size+t[t[p].son[1]].size+1;
}
inline void Build(int &res){
res=New(-inf);
t[res].son[1]=New(inf);
update(res);
}
inline void rotate(int &p,int k){
int temp=t[p].son[k^1];
t[p].son[k^1]=t[temp].son[k];
t[temp].son[k]=p;
p=temp;
update(t[p].son[k]);
update(p);
}
void insert(int &p,int val){
if(!p){
p=New(val);
return ;
}
int child=val<t[p].val?0:1;
insert(t[p].son[child],val);
if(t[p].dat<t[t[p].son[child]].dat)rotate(p,child^1);
update(p);
}
void remove(int &p,int val){
if(!p)return ;
if(t[p].val==val){
if(t[p].son[0]||t[p].son[1]){
if(!t[p].son[1]||t[t[p].son[0]].dat>t[t[p].son[1]].dat)
rotate(p,1),remove(t[p].son[1],val);
else rotate(p,0),remove(t[p].son[0],val);
update(p);
}
else p=0;
return ;
}
val<t[p].val?remove(t[p].son[0],val):remove(t[p].son[1],val);
update(p);
}
inline int getNext(int res,int val){
int p=res,next;
while(p){
if(val<t[p].val)next=t[p].val,p=t[p].son[0];
else p=t[p].son[1];
}
return next;
}
inline int getPre(int res,int val){
int p=res,pre;
while(p){
if(val>t[p].val)pre=t[p].val,p=t[p].son[1];
else p=t[p].son[0];
}
return pre;
}
inline int getRank(int p,int val){
if(t[p].val==val)return t[t[p].son[0]].size+1;
else if(val<t[p].val)return getRank(t[p].son[0],val);
else return t[t[p].son[0]].size+1+getRank(t[p].son[1],val);
}
inline bool check(int val,int l,int r){
return
((getRank(root[val],getNext(root[val],r))-getRank(root[val],getPre(root[val],l))-1)>((r-l+1)>>1))?true:false;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
n=read(),m=read();
int l,r,s,k,ans;
for(int i=1;i<=n;++i)
color[i]=read();
for(int i=1;i<=n;++i)
Build(root[i]);
build(1,1,n);
for(int i=1;i<=n;++i)
insert(root[color[i]],i);
while(m--){
l=read(),r=read(),s=read(),k=read();
for(int i=1;i<=k;++i)
c[i]=read();
ans=query(1,l,r,1,n).val;
ans=check(ans,l,r)?ans:s;
printf("%d\n",ans);
for(int i=1;i<=k;++i){
change(1,ans,c[i],1,n);
remove(root[color[c[i]]],c[i]);
color[c[i]]=ans;
insert(root[color[c[i]]],c[i]);
}
}
ans=query(1,1,n,1,n).val;
ans=check(ans,1,n)?ans:-1;
printf("%d\n",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t==EOF)return -1;if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();if(t==EOF)return -1;}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
int n=read();
int ans=0,cnt=0;
int res;
for(int i=1;i<=n;++i){
res=read();
if(res==ans)++cnt;
else{
if(cnt==0){
++cnt;
ans=res;
}
else{
--cnt;
}
}
}
printf("%d",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
NOIP2017D2T3大毒瘤题(可能没有2018年毒瘤)。
官方正解是树状数组,很多人写的动态开点线段树,然而我都不会只能写Splay。
我们为每一行1~m-1的位置开一棵Splay,为最后一列开一棵Splay。
因为Splay具有查询对应排名值的功能,所以我们按照题意进行模拟即可。
但是这个题的点数是\(300000^2=90000000000\)级别的,Splay显然开不下,我们在这里使用一种名为缩点Splay的黑科技,我们的每一个节点表示一段区间,就像是柯朵莉树一样,我们在使用一个点的时候将这个区间裂开,于是我们就解决了空间的问题。
缩点Splay有不同的写法,最为暴力的是将区间找到之后删除,再插入新的区间。
第二种是考虑点之间的大小关系,在Splay中直接将它拆开。
因为我比较菜所以写的第一种。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=400000,M=10000000;
int rt,root[N],tot;
int n,m;
long long ans;
bool flag;
int kth;
struct segment{
long long l,r,cnt;
segment(long long a,long long b){
l=a,r=b;
cnt=r-l+1;
}
segment(){
l=r=cnt=0;
}
};
struct node{
int son[2],father;
long long size;
segment val;
}t[M];
inline int get(int x){
return t[t[x].father].son[1]==x;
}
inline void update(int x){
t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+t[x].val.cnt;
}
inline void rotate(int x){
int y=t[x].father,z=t[y].father;
int k=get(x);
t[z].son[get(y)]=x;
t[x].father=z;
t[y].son[k]=t[x].son[k^1];
t[t[y].son[k]].father=y;
t[x].son[k^1]=y;
t[y].father=x;
update(y);
}
inline void build(long long l,long long r){
rt=++tot;
t[rt].val=segment(l,r);
update(rt);
}
inline void splay(int x,int goal){
int y,z;
while(t[x].father!=goal){
y=t[x].father,z=t[y].father;
if(z!=goal)
rotate(get(x)^get(y)?x:y);
rotate(x);
}
update(x);
if(!goal)rt=x;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void find(int k){
int x=rt;
kth=k;
while(1){
if(k<=t[t[x].son[0]].size)x=t[x].son[0];
else if(k<=t[t[x].son[0]].size+t[x].val.cnt){
if(!flag){
ans=t[x].val.l+k-t[t[x].son[0]].size-1;
kth-=ans-t[x].val.l;
flag=true;
}
splay(x,0);
return ;
}
else k-=t[t[x].son[0]].size+t[x].val.cnt,x=t[x].son[1];
}
}
inline void insert(int k,segment val){
int x=rt;
if(!rt){
rt=++tot;
t[rt].val=val;
update(rt);
return ;
}
--k;
if(k==0){
while(t[x].son[0])x=t[x].son[0];
t[x].son[0]=++tot;
t[tot].val=val;
t[tot].father=x;
splay(tot,0);
return ;
}
while(1){
if(k<=t[t[x].son[0]].size)x=t[x].son[0];
else if(k<=t[t[x].son[0]].size+t[x].val.cnt){
splay(x,0);
break;
}
else k-=t[t[x].son[0]].size+t[x].val.cnt,x=t[x].son[1];
}
x=rt;
if(t[x].son[1]){
x=t[x].son[1];
while(t[x].son[0])
x=t[x].son[0];
t[x].son[0]=++tot;
t[tot].val=val;
t[tot].father=x;
splay(tot,0);
}
else {
t[x].son[1]=++tot;
t[tot].val=val;
t[tot].father=x;
splay(tot,0);
}
}
inline void remove(){
int x=rt;
if(t[x].son[0]&&t[x].son[1]){
int y=t[x].son[0];
while(t[y].son[1])y=t[y].son[1];
splay(y,x);
t[y].son[1]=t[rt].son[1];
t[t[rt].son[1]].father=y;
t[y].father=0;
rt=y;
update(rt);
}
else{
if(t[x].son[0]){
t[t[x].son[0]].father=0;
rt=t[x].son[0];
update(rt);
}
else if(t[x].son[1]){
t[t[x].son[1]].father=0;
rt=t[x].son[1];
update(rt);
}
else rt=0;
}
}
//处理每一个询问
inline long long query(int x,int y){
segment res;
flag=false;
//如果在最后一列
if(y==m){
//切换到最后一列的平衡树
rt=root[0];
//找到这个人的位置并转到根
//同时找到答案
find(x);
//记录根节点的数字(一定是答案)
res=t[rt].val;
//删除根节点
remove();
//在列尾插入这个人
insert(n,segment(ans,ans));
root[0]=rt;
//更新根节点
}
else {
//切换至对应行的平衡树
rt=root[x];
//找到这个人并更新答案
find(y);
//记录根节点的区间
segment res=t[rt].val;
//删除根节点
remove();
//插回没有删除的区间
//kth表示该区间左端点的排名
//对于左边的区间,它应该在kth的位置上
if(ans>res.l)insert(kth,segment(res.l,ans-1));
//对于右边的区间,它应该在kth+(ans-res.l)+1的位置上
if(ans<res.r)insert(kth+ans-res.l,segment(ans+1,res.r));
//更新根节点
root[x]=rt;
//切换至最后一列的平衡树以查找插回行末的节点
rt=root[0];
//找到这个节点
find(x);
//记录这个数
res=t[rt].val;
//删掉这个数
remove();
//把这个数加入列尾
insert(n,segment(ans,ans));
//更新根节点
root[0]=rt;
//切换回来插入行末
rt=root[x];
insert(m-1,res);
root[x]=rt;
}
return ans;
}
int QAQ(){
freopen("qaq.in","r",stdin);
int q;
int x,y;
n=read(),m=read(),q=read();
for(int i=1;i<=n;++i){
build((long long)m*(i-1)+1,(long long)m*(i-1)+m-1);
root[i]=rt;
}
build(m,m);
root[0]=rt;
for(int i=2;i<=n;++i){
rt=root[0];
insert(i,segment((long long)m*(i-1)+m,(long long)m*(i-1)+m));
root[0]=rt;
}
while(q--){
x=read(),y=read();
printf("%lld\n",query(x,y));
}
return false;
}
}
int main(){
return orz::QAQ();
}
原题链接
NOIP2013D2T3大毒瘤题
暴力很容易写,暴力的去移动空白块就可以了,没写我也不知道能水多少分。
这个题如果没有多组询问的话其实BFS的复杂度也不是很差。
那我们来想一想怎么预处理一下。
我们考虑我们移动的过程,一定是把空白块移动到当前块的某个方向后再将当前块于空白块交换。所以我们可以用一个三元组表示当前状态,两维表示当前块的位置,第三维表示空白块在当前块的哪一个位置。我们可以用BFS预处理出状态之间的距离,我们建图后就可以跑最短路了。
需要注意的一个点是如果开始时块的位置就在目标位置,那么不用将空白块移过来。
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
namespace orz{
const int N=35,NN=4000,M=40000;
const int inf=2147483647;
const int dx[4]={1,0,-1,0};
const int dy[4]={0,1,0,-1};
struct point{
int x,y;
int cnt;
point(int a,int b,int c){
x=a,y=b,cnt=c;
}
};
struct node{
int x,dis;
bool operator<(const node&b)const{
return this->dis>b.dis;
}
node(int a,int b){
x=a,dis=b;
}
};
int n,m;
int head[NN],ver[M],next[M],edge[M],tot;
bool v[NN];
int map[N][N];
bool vis[N][N];
int dis[NN];
int orz[N][N];
int f[N][N][4][4];
inline void add(int x,int y,int z){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=z;
}
inline int num(int x,int y,int d){
return (m*(x-1)+y-1)*4+d+1;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void BFS(int sx,int sy){
int x,y,d;
int xx,yy;
queue<point>q;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
vis[i][j]=false;
q.push(point(sx,sy,0));
vis[sx][sy]=true;
orz[sx][sy]=0;
while(q.size()){
x=q.front().x;
y=q.front().y;
d=q.front().cnt;
q.pop();
for(int i=0;i<4;++i){
xx=x+dx[i];
yy=y+dy[i];
if(vis[xx][yy]||!map[xx][yy])continue;
q.push(point(xx,yy,d+1));
orz[xx][yy]=d+1;
vis[xx][yy]=true;
}
}
}
int QAQ(){
int x,y;
int xx,yy;
int k;
n=read(),m=read();k=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
map[i][j]=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int a=0;a<4;++a)
for(int b=0;b<4;++b)
f[i][j][a][b]=inf;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
if(!map[i][j])continue;
map[i][j]=false;
for(int a=0;a<4;++a){
x=i+dx[a];
y=j+dy[a];
if(!map[x][y])continue;
BFS(x,y);
for(int b=0;b<4;++b){
xx=i+dx[b];
yy=j+dy[b];
if(!vis[xx][yy])continue;
f[i][j][a][b]=orz[xx][yy];
}
}
map[i][j]=true;
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(map[i][j])
for(int a=0;a<4;++a)
for(int b=a+1;b<4;++b)
if(f[i][j][a][b]!=inf)
add(num(i,j,a),num(i,j,b),f[i][j][a][b]);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(!map[i][j])continue;
for(int z=0;z<4;++z){
x=i+dx[z];
y=j+dy[z];
if(!map[x][y])continue;
add(num(i,j,z),num(x,y,(z+2)%4),1);
}
}
}
int ex,ey,sx,sy,tx,ty;
while(k--){
bool flag=true;
for(int i=1;i<=num(n,m,3);++i)
dis[i]=inf,v[i]=false;
priority_queue<node>q;
ex=read(),ey=read();
sx=read(),sy=read();
tx=read(),ty=read();
if(sx==tx&&sy==ty){
printf("0\n");
continue;
}
map[sx][sy]=false;
BFS(ex,ey);
for(int i=0;i<4;++i){
x=sx+dx[i];
y=sy+dy[i];
if(vis[x][y]){
dis[num(sx,sy,i)]=orz[x][y];
q.push(node(num(sx,sy,i),orz[x][y]));
}
}
map[sx][sy]=true;
while(q.size()){
x=q.top().x;
q.pop();
if(v[x])continue;
v[x]=true;
if((x-1)/4+1==(m*(tx-1)+ty)){
printf("%d\n",dis[x]);
flag=false;
break;
}
for(int i=head[x];i;i=next[i]){
if(dis[x]+edge[i]<dis[ver[i]]){
dis[ver[i]]=dis[x]+edge[i];
q.push(node(ver[i],dis[ver[i]]));
}
}
}
if(flag)printf("-1\n");
}
return false;
}
}
int main(){
return orz::QAQ();
}
因为上午有NOI网络同步赛,所以把比赛咕了,下午看了题发现蒟蒻根本不会。
只有图连通才有合法路径
如果每一条边拆成两条,那问题就转化为了求删去不一样的两条边,还能存在欧拉路的方案数。
答案的上界是\(C_m^2\)
先来考虑没有自环的情况。
对于一个图,它存在欧拉路的条件就是所有点的度数都为偶数或只有两条边的度数为奇数。
因为每条边拆成了两个,所以开始所有的点度数都是偶数。
我们每拆掉一条边,它对两边的度数都有影响,如果我们拆了两条没有公共点的边,这会导致整张图出现4个奇度数点,肯定是不合法的。
所以选取边肯定是选取有相邻点的。
我们应该考虑每一个点作为拆除两条边的公共点。
拆除着两条边不影响公共点度数的奇偶性,而一定会改变两个点的奇偶性,随便取两条就可以了,贡献为\(C_{D_x}^2\)其中D表示度数(这里表示不算自环的度数)。
再来考虑有自环的情况
拆一条自环相当于没有贡献
对于拆的另一条边,它改变了两个点的度数,所以可以形成一个合法的方案。
而另外一条边是可以随便拆的。
我们这样算可能会重复算拆两个自环的,所以减一次只有自环的情况。
我们在算之前应该先判断图的连通性,但是这题中如果有一些没有边的自闭点的话也是可以的,我们可以记录每个边的一个端点,最后判断一下所有边是否在同一个连通块内。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=500000;
long long d[N];
long long cnt;
int poi[N];
int f[N];
int get(int x){
if(f[x]==x)return x;
return f[x]=get(f[x]);
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
int n;
long long m;
int x,y;
long long ans=0;
int cwy=0;
n=read(),m=read();
for(int i=1;i<=n;++i)
f[i]=i;
for(int i=1;i<=m;++i){
x=read(),y=read();
poi[i]=x;
if(x^y){
++d[x];
++d[y];
x=get(x);
y=get(y);
f[x]=y;
}
else{
++cnt;
}
}
cwy=get(poi[1]);
for(int i=2;i<=m;++i){
if(get(poi[i])^cwy){
printf("0");
return false;
}
}
for(int i=1;i<=n;++i)
ans+=d[i]*(d[i]-1)/2;
ans-=cnt*(cnt-1)/2;
ans+=cnt*(m-1);
printf("%lld",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
还没看题,咕了
因为输入只有一个k,所以我们考虑递推。
我们从小的状态推到大的状态。
以下是自己瞎想的
k+1之后相当于多了一层点
设当前新加的是第i层
多的点是\(2^{i-1}\)个点
多的边是上一层向这一层连的\(2^{i-1}\)条边以及这个点向上连的\(2^{i-1}-1\)条边。
肯定新加入的路径是单个点的路径
其它新加入的路径一定是既经过了以前的点又经过了新加的点
分情况来说就是新加入的点所为起点,中间点和终点
作为起点和终点是差不多的,因为每一条路径都可以倒过来,直接×2就可以。
同时作为起点与终点需要考虑
只有一维的话不好直接转移
我们设f[i][j]表示考虑了前k层,起始点在第k层的一个点作为起点方案数。
瞎想到这里发现不可做
于是去康了题解
原来是在下面加,加入了很多个点导致不可做,我们改变方法从上面加,即合并两个深度为i-1的子树并为它们连上一个根。
我们发现合并两棵子树还需要在两个子树之间通过根节点来合并一些路径,如果状态为一维还是很不好转移。
我们设f[i][j]表示i层炒鸡树,有j条不相交路径的方案数。
于是就可以枚举左右端点有几条路径来转移了。
最终答案为f[k][1];
设计
set t_Co=256 "启用256色
set number "启用行号
set tabstop=4 "Tab显示行数
syntax on "代码高亮(好像不用开)
colorscheme koehler
set ruler "尺子
set mouse=a "启用鼠标
"自动缩进三选一
set autoindent "与上一行相同的自动缩进
set smartindent "不知所云的自动缩进
set cindent "C风格的自动换行
"set expandtab tab自动转空格
set softtabstop=4 "tab转为多少个空格
set shiftwidth=4 "自动缩进时的tab大小
set nowrap "禁止自动折行
set hlsearch "搜索高亮
set noerrorbells "禁止提示音
set cc=80 "让你的代码更规范
inoremap ' ''<ESC>i
inoremap ( ()<ESC>i
inoremap [ []<ESC>i
inoremap " ""<ESC>i
inoremap { {}<ESC>i
map <F7> :r ~/Code/formwork/start.cpp <CR>
map <F12> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< <CR>
map <F8> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< && ./%< < %<.in <CR>
map <F5> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< <CR> :!gdb %< <CR>
map <F2> <ESC> :w <CR> :!gedit % <CR>
inoremap <F12> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< <CR>
inoremap <F8> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< && ./%< < %<.in <CR>
inoremap <F5> <ESC> :w <CR> :!g++ -g -Wall -lm % -o %< <CR> :!gdb %< <CR>
inoremap <F2> <ESC> :w <CR> :!gedit % <CR>
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=100;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
return false;
}
}
int main(){
return orz::QAQ();
}
又爆零了QAQ
这个出题人一定是失恋了
那一天我们在教室里许下约定。
我至今还记得我们许下约定时的欢声笑语。我记得她说过她喜欢吃饼干,很在意自己体重的同时又控制不住自己。她跟我做好了约定:我拿走她所有的饼干共N块,在从今天起不超过D天的时间里把所有的饼干分次给她,每天给她的饼干数要少于M以防止她吃太多。
当然,我们的约定并不是饼干的约定,而是一些不可言状之物。
现今回想这些,我突然想知道,有多少种方案来把饼干分给我的她。
我们看到这个题就自然而然的想到了组合数学,于是我就来组合ning干。
原问题可以转化为将一个数有序的分成每一块不大于m的d块的方案数。
我们可以发现这个d非常大,而n很小,所以会有很多天都是0,这不好。
我们除去是0的,只考虑不为零的。
我们枚举把n分为i块(每块不为零)
于是我们只要把对应的方案数乘上一个\((^d_i)\)就可以了,表示选出i天不为零。
如果不考虑m的限制,可以用插板法求出方案数为\((^{n-1}_{i-1})\)
但其中有一些不合法的方案,我们考虑容斥。
我们钦定j个块是多了的,那么有\((^i_j)\)种不同的选法,对于每一种选法,有\((^{n-j*(m-1)-1}_{i-1})\)种方案
容斥就完了
时间复杂度\(O(n^2)\)
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=2100;
const int MOD=998244353;
long long C[N][N];
long long fac[N];
long long ni[N];
long long dd[N];
inline long long read(){
long long a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline long long pow(long long x,long long y){
long long ans=1;
while(y){
if(y&1)ans=(ans*x)%MOD;
y>>=1;
x=(x*x)%MOD;
}
return ans%MOD;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
long long n,d,m;
long long res;
long long ans;
for(int i=0;i<=2000;++i)
C[i][0]=1;
for(int i=1;i<=2000;++i)
for(int j=1;j<=i;++j)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
fac[0]=1;
for(int i=1;i<=2000;++i)
fac[i]=fac[i-1]*i%MOD;
for(int i=1;i<=2000;++i)
ni[i]=pow(fac[i],MOD-2);
while(1){
n=read(),d=read(),m=read();
if(!n&&!d&&!m)return false;
res=ans=0;
dd[1]=d%MOD;
for(int i=2;i<=n;++i)
dd[i]=dd[i-1]*((d-i+1)%MOD)%MOD;
for(int i=1;i<=min(n,d);++i){
res=0;
res=C[n-1][i-1]%MOD;
for(int j=1;j<=i;++j){
if(j*(m-1)>n-1)break;
if(j&1)res=((res-(C[n-1-j*(m-1)][i-1]*C[i][j]%MOD))+MOD)%MOD;
else res=(res+C[n-1-j*(m-1)][i-1]*C[i][j]%MOD)%MOD;
}
res=dd[i]*res%MOD*ni[i]%MOD;
ans=(ans+res)%MOD;
}
printf("%lld\n",ans%MOD);
}
return false;
}
}
int main(){
return orz::QAQ();
}
她走的悄无声息,消失的无影无踪。
至今我还记得那一段时间,我们一起旅游,一起游遍山水。到了最终的景点,她却悄无声息地消失了,只剩我孤身而返。
现在我还记得,那个旅游区可以表示为一张由nnn个节点mmm条边组成无向图。我故地重游,却发现自己只想尽快地结束这次旅游。我从景区的出发点(即 1 号节点)出发,却只想找出最短的一条回路重新回到出发点,并且中途不重复经过任意一条边。
即:我想找出从出发点到出发点的小环。
对于个包含了1号节点的环,它是由一个1号节点和一条链组成,我们可以考虑怎么去求这样的一条链,我们选取每一个与1号节点相连的节点跑一遍最短路,然后更新答案,这样的时间复杂度是\(O(n^2\log n)\)因为如果所有点都与1号节点相连的话,那将会枚举n个点每个点nlogn的跑一次最短路。
我们考虑怎么优化,之前那样只是一个点到其他点的最短路,如果能一次跑出一个点集的点到另一个点集的最优解那将会很棒,我们需要保证的就是最优解的两个点至少有一次被分在了不同集合,因为每个数都是不一样的,所以我们按照每一个二进制位分组就可以,最后时间复杂的为\(O(n\log^2n)\)
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
namespace orz{
const int N=80000,M=200000;
const int inf=1147483647;
struct point{
int x,edge;
point(int a,int b){
x=a;
edge=b;
}
};
#define IT vector<point>::iterator
struct node{
int x,dis;
node(int a,int b){
x=a,dis=b;
}
inline bool operator<(const node&b)const{
return this->dis>b.dis;
}
};
int head[N],ver[M],edge[M],next[M],tot;
int dis[N],vis[N];
inline void add(int x,int y,int z){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=z;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t<'0'||t>'9');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
int t=read();
int x,y,z;
int ans;
while(t--){
vector<point>s;
int n,m;
ans=inf;
n=read(),m=read();
for(int i=1;i<=n;++i)
head[i]=0;
tot=0;
for(int i=1;i<=m;++i){
x=read(),y=read(),z=read();
if(x==1)s.push_back(point(y,z));
else if(y==1)s.push_back(point(x,z));
else add(x,y,z);
}
int k=0;
while((1<<k)<n)++k;
for(int i=0;i<k;++i){
priority_queue<node>q;
for(int j=1;j<=n;++j)
dis[j]=inf,vis[j]=false;
for(IT j=s.begin();j!=s.end();++j)
if(j->x&(1<<i))
q.push(node(j->x,j->edge)),dis[j->x]=j->edge;
while(q.size()){
x=q.top().x;
q.pop();
if(vis[x])continue;
vis[x]=true;
for(int j=head[x];j;j=next[j]){
y=ver[j];
if(dis[x]+edge[j]<dis[y]){
dis[y]=dis[x]+edge[j];
q.push(node(y,dis[y]));
}
}
}
for(IT j=s.begin();j!=s.end();++j)
if(!(j->x&(1<<i)))
ans=min(ans,dis[j->x]+j->edge);
}
if(ans^inf)printf("%d\n",ans);
else printf("-1\n");
}
return false;
}
}
int main(){
return orz::QAQ();
}
桌面上摊开着一些卡牌,这是她平时很爱玩的一个游戏。如今卡牌还在,她却不在我身边。不知不觉,我翻开了卡牌,回忆起了当时一起玩卡牌的那段时间。
每张卡牌的正面与反面都各有一个数字,我每次把卡牌按照我想的放到桌子上,而她则是将其中的一些卡牌翻转,最后使得桌面上所有朝上的数字都各不相同。
我望着自己不知不觉翻开的卡牌,突然想起了之前她曾不止一次的让我帮她计算最少达成目标所需要的最少的翻转次数,以及最少翻转达成目标的方案数。
(两种方式被认为是相同的当且仅当两种方式需要翻转的卡牌的集合相同)
如果我把求解过程写成程序发给她,她以后玩这个游戏的时候会不会更顺心一些?
考场上没想出来,最先想到的是直接DP,想了一会儿发现不可做,然后想到问题可以轻松的转换为一个2-SAT模型,但是发现计数和输出最优解都有困难,而且连边数也是\(n^2\)的。
下面来说正解,我们发现题目中给的x,y很像是一条边,所以我们由y向x连一条边(为什么从y向x连只是因为这样连边在后面的思考中会显得更自然)。
这里连的边虽然是有向的,但我们之后需要将它当作无向的来存图。
于是问题可以转化为,改变最小个数的边,使得所有的点都被至多一个点指向。
这张图被划分为了很多个连通块,连通块之间互不影响。所以我们一个一个考虑。运用鸽巢原理可以得出,如果一个n个点的连通块有大于n条边,那一定无解。
对于一个树的情况,我们发现只要选择了一个节点作为根,那么其他所有节点的方向都是定的,所以我们来一个换根DP就可以。
对于基环树的情况,我们只要确定了基环的方向,所有都是确定的,只需考虑两种情况就可以了。
因为一开始没有用脑子所以写了大力DFS,郭老师说直接DP就可以了,实际上只需要随便断一条边再考虑两边的点所谓根的情况就可以了。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=410000,M=1010000;
const long long MOD=998244353;
const int inf=2147483647;
int head[N],ver[M],edge[M],next[M],tot;
int f[N],c[N],in[N];
int cnt=0,sum[N],sumedge[N];
int stack[N],top;
int mem[N];
bool vis[N];
bool circle[N];
int d[N];
int ans;
int res;
long long resnum;
long long num;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=1;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=0;
}
void dfs(int x){
c[x]=cnt;
for(int i=head[x];i;i=next[i])
if(!c[ver[i]])
dfs(ver[i]);
}
void dp(int x,int father){
f[x]=0;
for(int i=head[x];i;i=next[i]){
if(ver[i]==father)continue;
dp(ver[i],x);
f[x]+=f[ver[i]]+edge[i];
}
}
void dky(int x,int father){
if(f[x]<res){
res=f[x];
resnum=1;
}
else if(f[x]==res){
resnum++;
}
for(int i=head[x];i;i=next[i]){
if(ver[i]==father)continue;
if(edge[i])f[ver[i]]=f[x]-1;
else f[ver[i]]=f[x]+1;
dky(ver[i],x);
}
}
void findCircle(int x,int eg){
if(mem[0])return;
stack[++top]=x;
vis[x]=true;
for(int i=head[x];i;i=next[i]){
if(i==eg)continue;
if(mem[0])continue;
if(vis[ver[i]]){
int y;
int ttop=top;
do{
y=stack[ttop--];
circle[y]=true;
mem[++mem[0]]=y;
}while(y!=ver[i]);
}
else findCircle(ver[i],i^1);
}
--top;
vis[x]=false;
}
void getHuan(int x,int eg){
vis[x]=true;
for(int i=head[x];i;i=next[i]){
if(!circle[ver[i]])continue;
if(i!=eg)res+=edge[i];
if(!vis[ver[i]]){getHuan(ver[i],i^1);break;}
}
}
void getOther(int x){
vis[x]=true;
for(int i=head[x];i;i=next[i]){
if(vis[ver[i]])continue;
if(circle[ver[i]]){
getOther(ver[i]);
}
else{
res+=edge[i];
getOther(ver[i]);
}
}
}
int QAQ(){
int t=read();
int x,y;
int mx;
while(t--){
int n=read();
//初始化
for(int i=1;i<=2*n;++i)
head[i]=0,c[i]=0,vis[i]=false,circle[i]=false;
cnt=0,ans=0,num=1,tot=1,mx=0;
//读入边
for(int i=1;i<=n;++i){
x=read(),y=read();
mx=max(mx,max(x,y));
in[i]=x;
add(x,y);
}
//求连通块
for(int i=1;i<=2*n;++i)
if(!c[i])
++cnt,dfs(i);
//清空连通块信息
for(int i=1;i<=cnt;++i)
sum[i]=sumedge[i]=0;
//加连通块点数,并记录入口点
for(int i=1;i<=2*n;++i)
++sum[c[i]],d[c[i]]=i;
//统计连通块边数
for(int i=1;i<=n;++i)
++sumedge[c[in[i]]];
//对每一个连通块分别处理
for(int i=1;i<=cnt;++i){
//无解情况
if(sumedge[i]>sum[i]){
ans=inf;
break;
}
//树的情况
else if(sumedge[i]==sum[i]-1){
res=inf;
dp(d[i],0);
dky(d[i],0);
num=(num*resnum)%MOD;
ans=ans+res;
}
else{
mem[0]=0;
findCircle(d[i],0);
res=0;
getHuan(mem[1],0);
ans+=min(res,mem[0]-res);
if(res==mem[0]-res)num=(num*2ll)%MOD;
res=0;
for(int i=1;i<=mem[0];++i)
vis[mem[i]]=false;
getOther(mem[1]);
ans+=res;
}
}
if(ans==inf)printf("-1 -1\n");
else printf("%d %lld\n",ans,num%MOD);
}
return false;
}
}
int main(){
return orz::QAQ();
}
BSGS板子题。
题目要求
\(B^L≡N\mod P\)
我们设\(L=im+j\)
\(B^{im+j}≡N\mod P\)
移项得\(B^{im}≡NB^{-j}\mod P\)
我们得到了这样的式子以后就可以用meet in the middle的思想,先处理处理出一边在用另一边来查。
因为一边走的大步一边走的小步所以叫BSGS。
但是总感觉和以前QY讲的BSGS不是一个东西。
这个m显然是取\(\sqrt{P}\)最优。
再拿个Hash表存一边就可以了。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;
namespace orz{
const int M=800000;
const int MOD=(1<<16)-1;
long long P,B,N;
int head[MOD+233],next[M],val[M],pos[M],tot;
inline long long read(){
long long a=1,b=0;char t;
do{t=getchar();if(t==EOF)exit(0);if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();if(t==EOF)exit(0);}while(t>='0'&&t<='9');
return a*b;
}
inline long long pow(long long x,int y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%P;
x=(x*x)%P;
y>>=1;
}
return ans%P;
}
inline void push(int x,int j){
next[++tot]=head[x%MOD+1],head[x%MOD+1]=tot,val[tot]=x,pos[tot]=j;
}
inline int find(int x){
for(int i=head[x%MOD+1];i;i=next[i])
if(val[i]==x)
return pos[i];
return -1;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
long long m;
long long ans;
long long res;
while(true){
P=read(),B=read()%P,N=read()%P;
for(int i=1;i<=MOD;++i)
head[i]=0;
tot=0;
ans=-1;
m=sqrt(P);
for(int j=m-1;j>=0;--j){
res=pow(B,j);
res=pow(res,P-2);
push(res*N%P,j);
}
for(int i=0;i*m<P;++i){
res=pow(B,i*m);
ans=find(res);
if(ans!=-1){
ans+=i*m;
break;
}
}
if(ans==-1)printf("no solution\n");
else printf("%lld\n",ans);
}
return false;
}
}
int main(){
return orz::QAQ();
}
标签:zjoi 维表 顺序 namespace 写法 expand 操作 tchar 遇到
原文地址:https://www.cnblogs.com/oiertkj/p/12203511.html