NOI online #1 提高组
序列
题意:
给定数组\(A,B\),有两种操作:
选择两个位置\(i,j\),使得\(a_i,a_j\)都加一或减一
选择两个位置\(i,j\)使得\(a_i\)加一,\(a_j\)减一,或者反过来。
给出的操作可以做任意次,问\(A\)数组能否变成\(B\)数组?
\(n,m\leq 10^5\)
题解:
考虑操作二,因为可以在里面任意分配权值,可以缩点。
然后把操作一对应的点连边,对于任意连通块:
如果是二分图,那么左部点和右部点的权值和之差不变,必须为\(0\)
如果不是二分图,那么连通块内\(a_i-b_i\)点数和必须是偶数。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=1e5+10;
int n,m;
int a[N],b[N],s[N],c[5];
int f[N],col[N];
vector<int> eg[N];
struct node
{
int x,y;
}q[N];
int num;
inline int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
inline void init()
{
num=0;
for(int i=1;i<=n;++i)
{
eg[i].clear();
f[i]=i;
s[i]=col[i]=0;
}
}
inline bool dfs(int now,int cc)
{
col[now]=cc;
c[cc]+=s[now];
bool flag=1;
for(int t:eg[now])
{
if(col[t]==col[now]) flag=0;
if(!col[t]&&!dfs(t,3-cc)) flag=0;
}
return flag;
}
inline void main()
{
int skx;cin>>skx;
while(skx--)
{
cin>>n>>m;
init();
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<=m;++i)
{
int opt,x,y;
cin>>opt>>x>>y;
if(opt==1) q[++num].x=x,q[num].y=y;
else f[find(x)]=find(y);
}
for(int i=1;i<=n;++i)
{
s[find(i)]+=a[i]-b[i];
}
for(int i=1;i<=num;++i)
{
int x=find(q[i].x),y=find(q[i].y);
eg[x].emplace_back(y);
eg[y].emplace_back(x);
}
bool flag=1;
for(int i=1;i<=n;++i) if(!col[i]&&find(i)==i)
{
c[1]=c[2]=0;
bool ok=dfs(i,1);
if(ok&&c[1]!=c[2]) {flag=0;break;}
if(!ok&&(c[1]^c[2])&1) {flag=0;break;}
}
if(flag) cout<<"YES\n";
else cout<<"NO\n";
}
}
}
signed main()
{
red::main();
return 0;
}
/*
*/
冒泡排序
题意:
给定一个排列\(P\)
有两个操作:
把\(p[x],p[x+1]\)交换位置。
问这个排列做\(k\)次冒泡排序后的逆序对数。
\(n,m\leq 10^5\)
题解:
假设一个序列的逆序对数是:
那么做一个冒泡排序后的逆序对数就是:
当然,和\(0\)取\(max\)
有了这个性质之后怎么做呢,其实不用真的去移位,因为第一位的逆序对数肯定\(<=1\),等于是把第一位剪掉就行了。
而\(k\)次之后就是
所有项和\(0\)取\(max\),然后求和,显然可以树状数组。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=3e5+10;
int n,m;
int a[N],p[N];
struct BIT
{
int tr[N];
inline void update(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
}
inline int query(int x)
{
int sum=0;
for(int i=x;i>=1;i-=lowbit(i)) sum+=tr[i];
return sum;
}
}T[2];
inline void main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>p[i];
T[0].update(p[i],1);
a[i]=i-T[0].query(p[i]);
}
for(int i=1;i<=n;++i) T[0].tr[i]=0;
for(int i=1;i<=n;++i) T[0].update(n-a[i],1),T[1].update(n-a[i],a[i]);
for(int i=1;i<=m;++i)
{
int opt,x;
cin>>opt>>x;
if(opt==1)
{
T[0].update(n-a[x],-1);
T[1].update(n-a[x],-a[x]);
T[0].update(n-a[x+1],-1);
T[1].update(n-a[x+1],-a[x+1]);
if(p[x]>p[x+1]) --a[x+1];
swap(p[x],p[x+1]);
swap(a[x],a[x+1]);
if(p[x]>p[x+1]) ++a[x+1];
T[0].update(n-a[x],1);
T[1].update(n-a[x],a[x]);
T[0].update(n-a[x+1],1);
T[1].update(n-a[x+1],a[x+1]);
}
else
{
int sum=T[0].query(n-x);
int val=T[1].query(n-x);
cout<<val-x*sum<<'\n';
}
}
}
}
signed main()
{
red::main();
return 0;
}
/*
*/
最小环
题意:
给\(n\)个数字,每次给一个数字\(k\),要求把\(n\)个数字排成一个环,让环上所有距离为\(k\)的两个数字的乘积的和最大。
\(n,m\leq 2*10^5,k\leq \lfloor \frac{n}{2}\rfloor\)
题解:
每个数字\(k\)会把整个环分成几个不相交的小环,为了让积之和最大,我们尽量让大的乘大的,小的乘小的,所以最大的数字都应该分给同一个环,然后类推。
在一个环内怎么排列呢?我们先把最大的数字放进去,然后去考虑第二大的,让当前数字尽量挨着大的数字放:
大概是以上模式插入,其中首尾是相接的。
数字\(k\)是怎么把长度为\(n\)的环分割的呢?其实就是分成个环,每个长度是\(\frac{n}{gcd(n,k)}\)。
所以不同的情况其实只有\(d(n)\)种,其中\(d(n)\)是\(n\)的约数个数。
所以时间复杂度\(O(nd(n))\sim O(n\sqrt{n})\)
还可以更优,注意对于只有一个环的情况,其他情况和它的区别只在两个环的交界处,对于\(g\)个环,只要\(O(\frac{n}{g})\)的时间算一下交界。
总复杂度\(\sum_{g|n}\frac{n}{g}\leq nH_n\),\(H_n\)是调和级数。约等于\(O(nlogn)\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=3e5+10;
int n,m;
int a[N];
int st[2][N],top[2];
int ret[N];
inline void main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;++i)
{
cin>>a[i];
sum+=a[i]*a[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=m;++i)
{
int k;cin>>k;
if(!k)
{
cout<<sum<<'\n';
continue;
}
int t=__gcd(n,k);
int d=n/t;
if(ret[d])
{
cout<<ret[d]<<'\n';
continue;
}
for(int j=0;j<t;++j)
{
int l=j*d+1,r=(j+1)*d;
top[0]=top[1]=0;
st[0][++top[0]]=a[r];
st[1][++top[1]]=a[r--];
while(r>l)
{
int b=0;
if(st[0][top[0]]<st[1][top[1]]) b=1;
ret[d]+=a[r]*st[b][top[b]];
st[b][++top[b]]=a[r--];
}
ret[d]+=a[l]*(st[0][top[0]]+st[1][top[1]]);
}
cout<<ret[d]<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
*/