NOI ONLINE提高組
1. 序列 題目網址 提高
題目描述
小D有一個長度爲n的整數序列, 他想通過若干次操作把它變成序列。
小D有m種可選的操作, 第i種操作可使用三元組描述:若 = 1, 則塔克以使與都加一或減一;若 = 2, 則她可以使減一, 加一, 或者是加一, 減一, 因此當時, 這種操作相當於沒有操作。
小D可以以任意順序執行操作, 且每種操作都可進行無數次。現在給定序列與所有操作, 請你幫他判斷是否存在一種方案能將變成。題目保證兩個序列長度都爲n。若方案存在輸出YES, 否則輸出NO.
題目思路
假設現在又a, b, c三個數。
則如果是操作1的話, 在a, b上加x, 在b, c上減去x, 就能做到在a上加x, c上減x, 一看, 正好是第二種操作。
操作二的話, 在a上+x, b上-x, b上+x, c上+x, 那麼看a, c, 又是操作一!!!!!
把操作1設爲邊權爲1, 操作2設爲邊權爲0;只要兩個點之間有操作, 那麼只要兩個邊有操作, 那麼就連邊。可見, 只要兩個點是聯通的, 就能在這兩個點身上進行操作。
如上面兩種轉移操作。
第一種轉移
第二種轉移
可見,
那麼, 就想到了並查集的操作, 每次吧中專點枚舉唯根節點即可。
還有判斷爲一的自環的問題, 用一個數組記錄, 足矣!!
代碼
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline long long readint(){
long long a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(long long x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
const int MaxN = 100005;
int fa[MaxN], val[MaxN];
inline int findSet(int a){
if(fa[a] == a) return a;
int root = findSet(fa[a]);
val[a] ^= val[fa[a]];
return fa[a] = root;
}
bool win[MaxN]; // 是否有長度爲1的自環
void unionSet(int a,int b,int c){
int x = findSet(a), y = findSet(b);
int dis = val[a]^c^val[b];
if(x == y) win[x] = win[x] or dis == 1;
else{
fa[x] = y, val[x] = dis;
win[y] = win[y] or win[x];
}
}
long long a[MaxN]; int n, m;
int main(){
// freopen("sequence.in","r",stdin);
// freopen("sequence.out","w",stdout);
for(int T=readint(); T; --T){
n = readint(), m = readint();
for(int i=1; i<=n; ++i){
a[i] = readint();
fa[i] = i, val[i] = 0;
win[i] = false;
}
for(int i=1; i<=n; ++i)
a[i] = readint()-a[i];
for(int opt,x; m; --m){
opt = readint()%2, x = readint();
unionSet(x,readint(),opt);
}
for(int i=1,rt; i<=n; ++i){
rt = findSet(i);
if(rt == i) continue;
if(val[i] == 1) // a[i]-=a[i],a[rt]-=a[i]
a[rt] -= a[i]; // 權值同時增加a[i] ...
else a[rt] += a[i]; // ... 需求便減少了
}
bool ok = true;
for(int i=1,rt; i<=n and ok; ++i){
rt = findSet(i);
if(rt != i) continue;
if(win[rt]) a[rt] %= 2;
if(a[rt] != 0) ok = false;
}
if(ok) puts("YES"); else puts("NO");
}
return 0;
}
2.冒泡排序 題目鏈接 提高
題目描述
給定一個1 ~ n 的排列, 接下來有m次操作, 操作共兩種:
1.交換操作:給定x, 把當前排列的第x個數與第x+1個數交換位置。
2.詢問操作:給定k, 請你求出當前排列經過k輪冒泡排序後逆序對的個數。對一長度爲n的排列進行一輪冒泡排序的僞代碼如下:
for i = 1 to n-1:
if p[i] > p[i+1]:
swap(p[i], p[i+1]);
題目分析
我們首先要明白冒泡排序的本質:
看一組例子:
4 1 3 2 5
3 4 2 1 5
3 2 1 4 5
2 1 3 4 5
1 2 3 4 5
每一次轉移的時候, 對於當前的這一個數,只有他前面沒有比它大的, 他纔會轉移。。。
那麼假如有x個要轉移的數, 一輪下來, 就減少了n-x個逆序對。
所以當前有一個數, 前面有y個數比她大的話, 他需要經過y+1輪冒泡排序才能去轉移。
用樹狀數組去預處理沒有 操作一時候冒泡排序每一輪逆序對的數量。
在考慮轉移;
用a數組表示輸入進去的數, b數組表示在當前下標位置上, 前面有多少個比他大。
設當前交換的數爲, , 爲當前這個位置前有幾個比它大的數。
當時, 那麼交換後初始逆序對個數會加一, 同時, 在x+1的位置上有多了個ax+1所以也會加1。但是當爲0時, 也就是x前沒有數比大時, 那麼這個逆序對就是無效的, 因爲下一輪冒泡接着就交換回去了。
當時, - 1.當爲0時, 就失效了。
差分思想查詢:前綴求和。
代碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,m,a[maxn],b[maxn],d[maxn];
long long c[maxn],ans;
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,long long val){
while(x<=n){
c[x]+=val;
x+=lowbit(x);
}
}
inline long long getsum(int x){
long long res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
int main(){
int opt,x,tmp=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
b[i]=i-1-getsum(a[i]);
ans+=b[i],++d[b[i]];
update(a[i],1);
}
memset(c,0,sizeof(c));
update(1,ans);
for(int i=0;i<n;++i){
tmp+=d[i];
update(i+2,-(n-tmp));
}
for(int i=1;i<=m;++i){
scanf("%d%d",&opt,&x);
x=min(x,n-1);
if(opt==1){
if(a[x]<a[x+1]){
swap(a[x],a[x+1]);
swap(b[x],b[x+1]);
update(1,1);
update(b[x+1]+2,-1);
b[x+1]++;
}
else{
swap(a[x],a[x+1]);
swap(b[x],b[x+1]);
update(1,-1);
b[x]--;
update(b[x]+2,1);
}
}
else printf("%lld\n",getsum(x+1));
}
return 0;
}
3.最小環 題目描述 提高
題目描述
給定一個長度爲n的正整數序列,下標從1開始編號。我們將該序列視爲一個首尾相連的環, 對於下標爲的兩個數, 他們的距離爲。
現在再給定m個整數, 對每個,你需要將上面的序列重新排列, 使得換上任意兩個距離爲的數字的乘積之和最大。
題目分析
k = 0
顯然, 每個數距離爲0的點就是他自己, 所以, 答案就爲每個數的平方和。
k = 1
看樣例, 我們的6個數中要使相鄰兩個數的乘積之和最大, 那麼先把6放進去。然後再放與他相鄰最大的5,4, 再放最大的2,3, 但大貼大, 小貼小。這樣很顯然是正確的, 因爲設5, 4, 爲一組, 設成a, b; 3,2爲一組, 設成c, d;那麼我們這一種方案就是
另一種就是, 顯然, 兩者一減, 前一種方案大。
k = 2
把他分成兩個環, 分別解決。
多了
環的長度爲n/gcd(n, k)
長度一樣答案一樣, 記得記憶化。
#include <bits/stdc++.h>
using namespace std;
map<int, long long> record;
int gcd(int a, int b)
{
int temp;
while (b)
{
temp = b;
b = a % b;
a = temp;
}
return a;
}(
long long a[200005];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%lld", a + i);
sort(a, a + n, greater<long long>()); //從大到小排序
for (int i = 0; i < m; i++)
{
int k;
scanf("%d", &k);
long long answer = 0;
if (k == 0) //特判,不然下面gcd會出錯
{
for (int p = 0; p < n; p++)
answer += a[p] * a[p];
printf("%lld\n", answer);
continue;
}
int ring = n / gcd(n, k); //環長
if (record[ring]) //記憶化
{
printf("%lld\n", record[ring]);
continue;
}
for (int p = 0; p < n; p += ring)
{ //對於每一個環,p記錄每個環最開始的點的下標
for (int x = 0, tp = p + 1; x < (ring - 2) / 2; x++, tp += 2) //一半環
answer += a[tp] * a[tp + 2];
for (int x = 0, tp = p; x < (ring - 1) / 2; x++, tp += 2) //另一半環
answer += a[tp] * a[tp + 2];
answer += a[p] * a[p + 1] + a[p + ring - 1] * a[p + ring - 2]; //最後處理兩個半環鏈接的問題
}
printf("%lld\n", answer);
record[ring] = answer; //記錄
}
return 0;
}