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\)
題解:
假設一個序列的逆序對數是:
\[b_1,b_2,b_3,…b_n \]那麼做一個氣泡排序後的逆序對數就是:
\[b_2-1,b_3-1,…,b_n-1,0 \]當然,和\(0\)取\(max\)
有了這個性質之後怎麼做呢,其實不用真的去移位,因為第一位的逆序對數肯定\(<=1\),等於是把第一位剪掉就行了。
\[b_1-1,b_2-1,b_3-1,…,b_n-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\)會把整個環分成幾個不相交的小環,為了讓積之和最大,我們儘量讓大的乘大的,小的乘小的,所以最大的數字都應該分給同一個環,然後類推。
在一個環內怎麼排列呢?我們先把最大的數字放進去,然後去考慮第二大的,讓當前數字儘量挨著大的數字放:
\[0,0,6,0,0,0\\ 0,0,6,5,0,0\\ 0,4,6,5,0,0\\ 0,4,6,5,3,0\\ 2,4,6,5,3,0\\ 2,4,6,5,3,1 \]大概是以上模式插入,其中首尾是相接的。
數字\(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;
}
/*
*/