洛谷P6185 [NOI Online 提高组]序列
问题相当于有一个序列(c_i=a_i-b_i),要进行若干次操作使得(c_i)每一项都为(0)。
有一档(t=2)的部分分,说明(2)肯定是比(1)好处理的。不妨先考虑只有(2)的情况。对于操作(2 u v),我们在(u,v)之间连一条无向边。这样整张图形成了若干个连通块。不难发现在每个连通块内部,对于任意两个节点(u,v),我们都可以实现(u exttt{++},v exttt{--})或(u exttt{--},v exttt{++})。也就是说在每个连通块内部,节点的权值可以相互转运。那么只有(2)操作时,有解当且仅当每个连通块内的权值和都是(0)。
在既有(1)也有(2)时,先用同样的方法把(n)个节点缩成若干个连通块,每个连通块的权值就是其中所有节点的权值和。这样,每个(1)操作就转化为了连通块之间的操作(特别地,当(1)操作的两个节点在同一连通块内时,这次操作的效果相当于让这个连通块的权值(pm2))。这样,把每个连通块看做一个点,则原问题转化为了对一个新序列,只有(1)操作的情况。
对于一次操作(1 u v),我们在(u,v)之间连一条无向边。发现连通块之间相互独立,因此全局有解当且仅当每个连通块都有解。考虑一个连通块。因为一次(1)操作会造成这个连通块内的权值和(pm2),因此有解的一个必要条件是连通块内所有节点的权值和为偶数。
考虑两个节点之间,若存在一条路径(可以有重边/自环)长度为偶数,则这两个节点的权值可以实现相互转运。如果一个连通块内存在奇环,则任意两个节点的距离都可以是偶数,此时该连通块有解。否则这个连通块是一个二分图。我们对它黑白染色后,每条边一定连接两个不同颜色的节点。发现我们一次(1)操作可以让黑、白点的权值和同时(+1)或同时(-1),且同种颜色的节点之间距离一定是偶数。因此此时有解当且仅当黑、白点的权值和相等。
时间复杂度(O(n))。
参考代码:
//problem:P6185
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=1e5;
int n,m,a[MAXN+5],fa[MAXN+5],cnt_e,cnt_id,id[MAXN+5],col[MAXN+5];
pii e[MAXN+5];
int get_fa(int x){return x==fa[x]?x:(fa[x]=get_fa(fa[x]));}
vector<int>G[MAXN+5];
ll val[MAXN+5],Sum,sum1,sum2;
bool flag;
void dfs(int u){
if(col[u]==1)sum1+=val[u];else sum2+=val[u];
Sum+=val[u];
for(int i=0;i<(int)G[u].size();++i){
int v=G[u][i];
if(col[v]){
if(col[v]!=3-col[u])flag=0;
}else{
col[v]=3-col[u];dfs(v);
}
}
}
int main() {
int Testcases=read();while(Testcases--){
n=read();m=read();
for(int i=1;i<=n;++i)fa[i]=i,a[i]=read();
for(int i=1;i<=n;++i)a[i]-=read();
cnt_e=0;
for(int i=1;i<=m;++i){
int t=read(),u=read(),v=read();
if(t==1){
e[++cnt_e]=mk(u,v);
}else{
u=get_fa(u),v=get_fa(v);
if(u!=v)fa[u]=v;
}
}
cnt_id=0;
for(int i=1;i<=n;++i)if(get_fa(i)==i)id[i]=++cnt_id;
for(int i=1;i<=cnt_id;++i)vector<int>().swap(G[i]),col[i]=0,val[i]=0;
for(int i=1;i<=n;++i)val[id[get_fa(i)]]+=a[i];
for(int i=1;i<=cnt_e;++i){
int u=id[get_fa(e[i].fst)],v=id[get_fa(e[i].scd)];
G[u].pb(v);G[v].pb(u);
}
bool yes=1;
for(int i=1;i<=cnt_id;++i)if(!col[i]){
flag=1;Sum=sum1=sum2=0;
col[i]=1;dfs(i);
if(Sum%2!=0){
yes=0;
break;
}
if(flag&&sum1!=sum2){//是二分图
yes=0;
break;
}
}
if(yes)puts("YES");else puts("NO");
}
return 0;
}
洛谷P6186 [NOI Online 提高组]冒泡排序
考虑一轮冒泡排序会对序列产生什么影响。设位置(i)前面值大于(a_i)的位置数量为(s_i)。则一轮冒泡排序后,首先所有(s_i)向左移一位,然后所有大于(0)的(s_i)都减(1)。即:(s_i=max(s_{i+1}-1,0))。
维护两个树状数组。下标均为(s_i)的值域(显然所有(s_i<n))。第一个树状数组存(s_j=i)的(j)的个数,第二个树状数组存(s_j=i)的(s_j)之和。交换操作相当于是在树状数组上做简单的单点修改。查询时,显然只有(s_i>k)的这些位置会影响答案(其他位置减(k)次之后取(max)后就都变为(0)了)。用第二个树状数组求后缀和,减去第一个树状数组的后缀和乘以(k),就是答案了。
时间复杂度(O(mlog n))。
参考代码:
//problem:P6186
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=2e5;
int n,m,a[MAXN+5],s[MAXN+5];
struct FenwickTree{
ll c[MAXN+5];
void modify(int p,int v){
if(!p)return;
for(;p<=n;p+=(p&(-p)))c[p]+=v;
}
ll query(int p){
ll res=0;
for(;p;p-=(p&(-p)))res+=c[p];
return res;
}
void clr(){
memset(c,0,sizeof(c));
}
FenwickTree(){}
}T,T1;
int main() {
n=read();m=read();
for(int i=1;i<=n;++i)a[i]=read(),s[i]=T.query(n)-T.query(a[i]),T.modify(a[i],1);
T.clr();
for(int i=1;i<=n;++i)T.modify(s[i],1),T1.modify(s[i],s[i]);
while(m--){
int op=read(),x=read();
if(op==1){
T.modify(s[x],-1);T.modify(s[x+1],-1);
T1.modify(s[x],-s[x]);T1.modify(s[x+1],-s[x+1]);
int t2=s[x]+(a[x+1]>a[x]);
int t1=s[x+1]-(a[x]>a[x+1]);
s[x]=t1;s[x+1]=t2;
swap(a[x],a[x+1]);
T.modify(s[x],1);T.modify(s[x+1],1);
T1.modify(s[x],s[x]);T1.modify(s[x+1],s[x+1]);
//cout<<"* ";for(int i=1;i<=n;++i)cout<<s[i]<<" ";cout<<endl;
}else{
if(x>=n){printf("%d
",0);continue;}
ll res=T1.query(n)-T1.query(x)-(ll)x*(T.query(n)-T.query(x));
printf("%lld
",res);
}
}
return 0;
}
洛谷P6187 [NOI Online 提高组]最小环
观察样例,猜测构造策略。
首先,距离为(k)的位置一定构成了若干个长度相等的环,每个环上相邻两个点距离都是(k),环与环之间不存在距离为(k)的点。因此每个环是独立的。容易发现,每个环的长度都是(frac{n}{gcd(n,k)})。
原则上我们要尽量把大的数放在一起,这样才能使乘积之和最大化。
因此如果环长为(len),则一定把前(len)大的数放在同一个环中,接下来(len)个数放下一个环中,以此类推......。
对于一个环,设它上面的数字分别为(s_1,dots,s_{len}),则它对答案的贡献是(sum_{i=1}^{len}s_is_{imod len+1})。我们任选一个位置放最大的数,然后在它两边分别放次大的和更次大的数。例如,(len=5)时的最优放法为:((2,4,5,3,1))。
因为可能的环长一定是(n)的约数,我们预处理每个(len)的答案。总时间复杂度(O(nsqrt{n}))。
参考代码:
//problem:P6187
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
if(S==T){
T=(S=buf)+fread(buf,1,MAXN,stdin);
if(S==T)return EOF;
}
return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#endif
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll readll(){
ll f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
/* ------ by:duyi ------ */ // myt天下第一
const int MAXN=2e5;
int n,m,a[MAXN+5],s[MAXN+5],len;
ll ans[MAXN+5];
inline int pre(int i){return i==1?len:i-1;}
inline int nxt(int i){return i==len?1:i+1;}
inline int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int main() {
n=read();m=read();
for(int i=1;i<=n;++i)a[i]=read();
sort(a+1,a+n+1);
for(len=1;len<=n;++len)if(n%len==0){
//环长:len
//cout<<"len "<<len<<endl;
int cur=n;
while(cur){
int l=1,r=1;s[1]=a[cur--];
for(int i=2;i<=len;++i){
if(i&1)l=pre(l),s[l]=a[cur--];
else r=nxt(r),s[r]=a[cur--];
}
//for(int i=1;i<=len;++i)cout<<s[i]<<" ";cout<<endl;
for(int i=1;i<=len;++i)ans[len]+=(ll)s[i]*s[nxt(i)];
}
}
while(m--){
int k=read();
int len=n/gcd(n,k);
printf("%lld
",ans[len]);
}
return 0;
}