CSP-S模拟13排序 Xorum 有趣的区间问题 无聊的卡牌问题
T1【构造+规律】:给你一个排列,要你求逆序对数量,把原序列的逆序对位置当成交换,进行任意排列使得最后序列升序。(n<=1000)
一:排列的实质是id[i]=i的一一对应,问题互相转化会更简单。原序列i-->a[i]转化成a[i]-->i,b[a[i]]=i
发现强制原序列逆序对已经通过下标保证,满足交换次数=逆序对数的操作方法就是冒泡排序一遍。
二:如果需要把每个逆序对用上,说明每次操作只能消除一个逆序对,所以每次交换相邻的值的位置,不改变其他的逆序对。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1100;
int n,a[N],b[N];
int cnt;
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n)a[i]=re(),b[a[i]]=i;
_f(i,1,n)
_f(j,i+1,n)
if(a[j]<a[i])cnt++;
chu("%d\n",cnt);
_f(i,1,n)//每次把b里面i换到i位置
{
int pos=0;
_f(j,i+1,n)
if(b[j]==i)
{pos=j;break;}
f_(j,pos,i+1)
{
swap(b[j],b[j-1]);
chu("%d %d\n",min(b[j],b[j-1]),max(b[j],b[j-1]));
}
}
return 0;
}
/*
5
5 1 3 4 2
10
7 3 4 2 5 6 10 9 8 1
*/
T2【构造/随机化】给你一个数x(x<=1e6),是奇数,要求你每次选出已有的2个数,(1)c=a^b(2)c=a+b
产生新的数,最后产生1,操作次数不超过1e5。
一:就是让产生的数相差1而且必须是奇数>偶数<,考虑如果gcd(a,b)=1,ax-by=1有解,所以先随机产生(a,b),然后exgcd求解,最后验证是否合法。提高正解率:先算加,再算异或,减少重复。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=510;
ll g[100000+100];int cnt,tot;
struct Operation
{
ll op,x,y;
}e[100000+100];
map<ll,bool>mp;
inline void print(int len)
{
chu("%d\n",len);
_f(i,1,len)
{
chu("%lld %lld %lld\n",e[i].op,e[i].x,e[i].y);
}
exit(0);
}
ll xx,yy;
inline ll exgcd(ll ar,ll br)
{
if(br==0)
{
xx=1,yy=0;return ar;
}
ll Gcd=exgcd(br,ar%br);
ll tmp=xx;
xx=yy;
yy=tmp-ar/br*yy;
return Gcd;
}
inline ll gcd(ll ar,ll br)
{
if(!br)return ar;
return gcd(br,ar%br);
}
inline void Deal_end(ll a1,ll a2)//就是2个数互质解方程
{
// chu("has find:%lld %lld\n",a1,a2);
exgcd(a1,a2);//求解线性
//x+=b/a*gcd
while(xx<0)xx+=a2,yy-=a1;
while(yy>0)yy-=a1,xx+=a2;
yy=-yy;
// chu("is a solution:%lld %lld\n",xx,yy);
// chu("up to now time is:%d\n",tot);
// _f(i,1,tot)
// chu("%d %d %d\n",e[i].op,e[i].x,e[i].y);
// chu("fuck you wrong!\n");
// _f(i,1,xx-1)e[++tot]=(Operation){1,a1*i,a1};
if(((a1*xx)^(a2*yy))!=1)return ;
int lgx=log2(xx),lgy=log2(yy);
if(lgx+(xx-((1<<((int)log2(xx)))))+lgy+(yy-((1<<((int)log2(yy)))))+tot>100000)return;
//if(xx+yy+tot>100000)return;
for(rint i=1;i*2<=xx;i<<=1)
{
e[++tot]=(Operation){1,a1*i,a1*i};
}
int has=1<<((int)log2(xx));
int lef=xx-has;
_f(i,1,lef)e[++tot]=(Operation){1,a1*(has+i-1),a1};
for(rint i=1;i*2<=yy;i<<=1)
{
e[++tot]=(Operation){1,a2*i,a2*i};
}
has=1<<((int)log2(yy));
lef=yy-has;
// _f(i,1,yy-1)e[++tot]=(Operation){1,a2*i,a2};
_f(i,1,lef)e[++tot]=(Operation){1,a2*(has+i-1),a2};
e[++tot]=(Operation){2,a1*xx,a2*yy};
// chu("tot has:%d\n",tot);
print(tot);
return ;
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
g[++cnt]=re();
mp[g[cnt]]=1;
if(g[cnt]==1)
{
chu("%d",0);
return 0;
}
g[cnt+1]=g[cnt]+g[cnt];++cnt;
e[++tot]=(Operation){1,g[cnt-1],g[cnt-1]};
mp[g[cnt]]=1;
int add=1;
if(gcd(g[cnt],g[cnt-1])==1)
{
Deal_end(cnt,cnt-1);
}
while(cnt<=100000+10)//开始找
{
int ad=add;add=0;int nowcnt=cnt;
// chu("new_round:%d\n",tot);
// chu("the new round")
_f(ir,1,ad)//把last的数和新的
{
int i=nowcnt-ir+1;
_f(j,1,nowcnt-ad)
{
if(gcd(g[i],g[j])==1)
{
Deal_end(g[i],g[j]);
}
ll mayin=g[j]+g[i];
if(mp.find(mayin)==mp.end())
{
g[++cnt]=mayin;
e[++tot]=(Operation){1,g[i],g[j]};
mp[mayin]=1;
++add;
}
mayin=g[j]^g[i];
if(mp.find(mayin)==mp.end())
{
g[++cnt]=mayin;
e[++tot]=(Operation){2,g[i],g[j]};
mp[mayin]=1;
++add;
}
}
}
}
return 0;
}
/*
拿出2个数,异或放进去,和放进去,先查询是否已经满足要求,如果满足,就停止;
如果不满足,把新的数异或一遍,加一遍放进去,记录操作数
208899
*/
二:构造一组解。构造方法就是"1101"-->"1101000"-->+ and -->-->only 1-->to delete 1 from 1101000-->to delete 1from 1101-->a new smaller question\(O(logn^2)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=510;
struct Op
{
ll op,x,y;
}e[100000+100];
int cnt;
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
ll a=re();
if((a>>1)&1)//如果不是0
{
e[++cnt]=(Op){1,a,a};
e[++cnt]=(Op){2,a*2,a};
a=(a*2)^a;
}
// //得到了一个最高位是hig+1,剩下全部是0的数
int _1=0;
ll tmp=a;
while(tmp)
{
tmp-=((tmp)&(-tmp));
_1++;
}
_1--;
// chu("many:%d a:%lld\n",_1,a);
_f(k,1,_1)//需要重复进行这么多次的操作把前面的1消掉
{
int hig=-1;
ll tmp=a;
// chu("tmp:%lld\n",tmp);
while(tmp){tmp>>=1;hig++;}//最高位1是第几位
// chu("out?:a:%lld\n",a);
// chu("hig:%d\n",hig);
_f(i,1,hig)
{
e[++cnt]=(Op){1,a<<(i-1),a<<(i-1)};
}
ll b=a<<hig;//就是上面造出来的
// chu("b:%lld\n",b);
//chu("b:%lld\n",b);
e[++cnt]=(Op){1,a,b};
e[++cnt]=(Op){2,a,b};
e[++cnt]=(Op){2,(a+b),(a^b)};//if(cnt==16)chu("wrong:%d+%d ^ %d^%d:%d\n",a,b,a,b,(a+b)^(a^b));
ll wonder=(a+b)^(a^b);
// chu("wondrt:%lld\n",wonder);
int now_hig=hig+hig;
ll ap=b;
_f(j,hig+2,now_hig)
{
e[++cnt]=(Op){1,wonder,wonder};
// chu("cnt;%d\n",cnt);
wonder=wonder*2;
// chu("wonder:%lld\n",wonder);
if((1ll<<j)&ap)
{
// chu("opp:%lld\n",1ll<<j);
e[++cnt]=(Op){2,ap,wonder};ll opp=1ll<<j;
// chu("from:%lld-->%lld\n",ap,(ap^opp));
ap=ap^opp;
}
}
//ap
// chu("ap:%lld\n",ap);
e[++cnt]=(Op){2,ap,a};
a=(a^ap);
// chu("a:%lld\n",a);
}
chu("%d\n",cnt);
_f(i,1,cnt)
{
chu("%lld %lld %lld\n",e[i].op,e[i].x,e[i].y);
}
return 0;
}
/*
*/
T3:【分治】给定序列A,求\(sigma([l,r]),popcount(Max)==popcount(Min)\)(n<=1e6)
经典分治算法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1000000+100;
int n;
ll a[N],mx[N],mxp[N],mi[N],mip[N],cnp[N],pop[N];
inline ll solve(int l,int r)
{
if(l==r)return 1;
int mid=(l+r)>>1;
ll Mx=a[mid],Mxp=mid,Mi=a[mid],Mip=mid;
mx[mid]=Mx,mi[mid]=Mi,mxp[mid]=mid,mip[mid]=mid;
f_(i,mid-1,l)
{
if(a[i]>Mx)
{
Mx=a[i];Mxp=i;
}
if(a[i]<Mi)
{
Mi=a[i];Mip=i;
}
mxp[i]=Mxp;mx[i]=Mx;
mip[i]=Mip;mi[i]=Mi;
}
Mx=a[mid+1],Mi=a[mid+1],Mxp=mid+1,Mip=mid+1;
mxp[mid+1]=mid+1,mip[mid+1]=mid+1;
mx[mid+1]=a[mid+1];mi[mid+1]=a[mid+1];
_f(i,mid+2,r)
{
if(a[i]>Mx)
{
Mx=a[i];Mxp=i;
}
if(a[i]<Mi)
{
Mi=a[i];Mip=i;
}
mxp[i]=Mxp;mx[i]=Mx;
mip[i]=Mip;mi[i]=Mi;
}
int R,L;
R=mid;
ll sum=0;
f_(i,mid,l)
{
if(pop[mxp[i]]!=pop[mip[i]])continue;
while(R<r&&mx[R+1]<=mx[i]&&mi[R+1]>=mi[i])R++;
sum+=R-(mid+1)+1;
}
L=mid+1;
_f(i,mid+1,r)
{
if(pop[mxp[i]]!=pop[mip[i]])continue;
while(L>l&&mx[L-1]<=mx[i]&&mi[L-1]>=mi[i])L--;
sum+=mid-L+1;
}
R=mid;L=mid+1;
f_(i,mid,l)//mx在左边,mi在右边
{
while(R<r&&mx[R+1]<mx[i])cnp[pop[mip[R+1]]]++,R++;
while(L<=R&&mi[L]>=mi[i])cnp[pop[mip[L]]]--,L++;
// if(l==9&&r==10) chu("mx:%d contrl:%d--%d\n",i,L,R);
sum+=cnp[pop[mxp[i]]];
}
while(L<=R)cnp[pop[mip[L]]]--,L++;
// _f(i,0,3)chu("cnp[%d]:%d\n",i,cnp[i]);
L=mid+1,R=mid;
_f(i,mid+1,r)
{
while(L>l&&mx[L-1]<mx[i])cnp[pop[mip[L-1]]]++,L--;//,chu(" %d++",mip[L]);
while(L<=R&&mi[R]>=mi[i])cnp[pop[mip[R]]]--,R--;
sum+=cnp[pop[mxp[i]]];
// chu("sumadd:%d\n",cnp[pop[mx[i]]]);
// chu("%d contrl:%d--%d\n",i,L,R);
}
while(L<=R)cnp[pop[mip[L]]]--,L++;
// _f(i,0,3)chu("cnp[%d]:%d\n",i,cnp[i]);
// chu("%d--%d:dev:%d\n",l,r,sum);
return sum+solve(l,mid)+solve(mid+1,r);
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n)
{
a[i]=re();
ll tmp=a[i];
while(tmp)
{
pop[i]++;
tmp-=(tmp&(-tmp));
}
}
//_f(i,1,n)chu("pop[%d]:%d\n",i,pop[i]);
chu("%lld",solve(1,n));
return 0;
}
/*
10
0 5 7 3 9 10 1 6 13 7
*/
T4【贪心+构造+图的应用】A和B轮流从6n个数里挑选3个连续段,给定A的挑选序列,求一种取拿方案。(n<=200)
一:随便暴力,枚举A和B每次拿出的数。\(O(2n!)\)理论上,但是因为合法方案很多,而且可以随机化搜索顺序,所以可以AK。
用链表优化查找顺序,对于冗余状态直接跳过其实就是大力nxt[pre[i]]=nxt[nxt[nxt[i]],pre[nxt[nxt[nxt[i]]]]=pre[i]......
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1200+100;
int n,A[N],has[N],vis[N];
struct Op
{
int x,y,z;
}e[N];
set<int>s;
set<int>::iterator it;
inline void dfs(int op,int gan)
{
if(gan==n*2)
{
_f(i,1,gan)
{
chu("%d %d %d\n",e[i].x,e[i].y,e[i].z);
}
exit(0);
}
// chu("try:%d %d\n",op,gan);
int ans[4],tot;
if(op==1)//该A选了
{
int siz=(6*n-gan*3)/3;
for(int ty=1;ty<=siz;ty++)
{
// chu("siz:%d\n",siz);
int now=0;int yes=0;tot=0;
int ok=0;
for(it=s.begin();it!=s.end();++it)
{
int id=*it;
// chu("id:%d(has:%d vis:%d)\n",id,has[id],vis[id]);
if(has[id]&&!vis[id])
{
++now;ans[++tot]=id;
if(now==3)
{
ok++;
if(ok==ty)
{
yes=1;break;
}
else
{
now=0;tot=0;
}
}
}
else now=0,tot=0;
}
// chu("find?:%d\n",yes);
if(yes)
{
s.erase(ans[1]);
s.erase(ans[2]);
s.erase(ans[3]);
vis[ans[1]]=vis[ans[2]]=vis[ans[3]]=1;
e[gan+1]=(Op){ans[1],ans[2],ans[3]};
dfs(!op,gan+1);
s.insert(ans[1]);
s.insert(ans[2]);
s.insert(ans[3]);
vis[ans[1]]=vis[ans[2]]=vis[ans[3]]=0;
}
}
}
else
{
int siz=(6*n-gan*3)/3;
for(int ty=siz;ty>=1;ty--)
{
int now=0;int yes=0;tot=0;
int ok=0;
for(it=s.begin();it!=s.end();++it)
{
int id=*it;
if(!has[id]&&!vis[id])
{
++now;ans[++tot]=id;
if(now==3)
{
ok++;
if(ok==ty)
{
yes=1;break;
}
else
{
now=0;tot=0;
}
}
}
else now=0,tot=0;
}
if(yes)
{
s.erase(ans[1]);
s.erase(ans[2]);
s.erase(ans[3]);
vis[ans[1]]=vis[ans[2]]=vis[ans[3]]=1;
e[gan+1]=(Op){ans[1],ans[2],ans[3]};
dfs(!op,gan+1);
s.insert(ans[1]);
s.insert(ans[2]);
s.insert(ans[3]);
vis[ans[1]]=vis[ans[2]]=vis[ans[3]]=0;
}
}
}
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n*3)
{
int x=re();
has[x]=1;
}
_f(i,1,6*n)s.insert(i);
dfs(1,0);
return 0;
}
/*
vis[]:B不能拿的牌(A序列+已经拿的)
set<>s:A可以拿的牌(删掉BA已经拿的)
到A拿,不合法的情况:
set里面没有连续段了或者set里面连续段不存在 在A序列里出现过
到B:
set里面没有连续段了或者set里面连续段不存在 没有在A里出现过
如果成功分配:
用三元组记录方案。
2
2 3 4 9 10 11
19 20 21
24 25 26
11 12 13
27 28 29
1 2 3
14 15 16
18 22 23
6 7 8
4 5 9
10 17 30
*/
二:考虑已经完成取拿的序列1--6*n,每个位置的颜色0/1代表是由谁拿的,可以根据取拿的位置限制关系构成括号序列,也即是森林,父亲必须比儿子后拿,儿子拿完父亲才可以拿,实际意义就是([]{}),拿连续的一段,"("必须在"[""}"之后拿。
建立完树林,首先树林的性质就是父亲儿子颜色不一样,同一个儿子颜色一样。
考虑一种拓扑序顺序进行方案构造。有解是需要通过顺序保障的,因为最后一个必须是拿“0”(后手),这个0必须是根而且最后拿,所以可以每次都轮流拿“1”“0”叶子(随时更新),但是只要不是最后一次拿,就不拿剩下的最后一个是0的根,这样一定有解。
怎么理解?
(1)【最后留一个root_0】贪心:尽量拿还是叶子的,这样可以解锁更多的节点,更有可能合法。
(2)【不拿root拿叶子已定有】倒着构造:每次每个人选择一个属于自己的根,这样叶子就是随便选的
也可以举出例子:
1<--0-->1
0<--1-->0
这样的树林如果1先拿根,最后就没0可以选了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1210;
int id[N],stk[N][3],tot,top,p[N][3],siz[N];
int fa[N],son[N],vis[N],du[N],cor[N],n,se[N],a[N];
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,3*n)
{
a[i]=re();//读入对应的操作数
se[a[i]]=1;//标记对应数字位置的颜色
}
//stk:原编号(球球)
_f(i,1,6*n)//开始模拟建立树林的过程
{
// chu("rod:%d\n",i);
// chu("stk[top][0]:%d\n",stk[top][0]);
if(!top||se[stk[top][0]]!=se[i])
{
// chu("N ew\n");
top++;
siz[top]=0;
stk[top][siz[top]++]=i;//节点放进去
id[top]=++tot;//给它编号
}
else//连续3个凑
{
//chu("insert\n");
stk[top][siz[top]++]=i;
if(siz[top]==3)
{
fa[id[top]]=id[top-1];
cor[id[top]]=se[stk[top][0]];
memcpy(p[id[top]],stk[top],sizeof(p[id[top]]));//把原来的编号都放进去,3个
top--;//没了
}
}
}
// chu("tot:%d\n",tot);
_f(i,1,n<<1)//2*n个节点?
du[fa[i]]++;//chu("(%d %d %d)cor:%d fa%d\n",p[i][0],p[i][1],p[i][2],cor[i],fa[i]);
int _0=0;
_f(i,1,n<<1)
if(fa[i]==0&&cor[i]==0)_0++;
_f(i,1,n<<1)//开始游戏!
{
// chu("lef:%d\n",_0);
int cr=i&1;//应该拿出什么颜色
int goal=0;
_f(j,1,tot)//看看哪些可以
{
//chu("%d %d %d\n",du[j],vis[j],cor[j]);
if(du[j]==0&&vis[j]==0&&cor[j]==cr)
{
// chu("he will find\n");
if(i<(n*2)&&cr==0&&_0==1&&fa[j]==0)continue;
// chu("he has find\n");
goal=j;break;
}
}
chu("%d %d %d\n",p[goal][0],p[goal][1],p[goal][2]);
du[fa[goal]]--;vis[goal]=1;
if(fa[goal]==0&&cr==0)_0--;//表示根少了一个
}
return 0;
}
/*
2
2 3 4 9 10 11
*/