小D有一个长度为n的整数序列 a 1... n a_{1...n} a1...n, 他想通过若干次操作把它变成序列 b i b_i bi。
小D有m种可选的操作, 第i种操作可使用三元组 ( t i , u i , v i ) (t_i, u_i, v_i) (ti,ui,vi)描述:若 t i t_i ti = 1, 则塔克以使 a u i a_{u_i} aui与 a v i a_{v_i} avi都加一或减一;若 t i t_i ti = 2, 则她可以使 a u i a_{u_i} aui减一, a v i a_{v_i} avi加一, 或者是 a u i a_{u_i} aui加一, a v i a_{v_i} avi减一, 因此当 u i = v i u_i = v_i ui=vi时, 这种操作相当于没有操作。
小D可以以任意顺序执行操作, 且每种操作都可进行无数次。现在给定序列与所有操作, 请你帮他判断是否存在一种方案能将 a i a_i ai变成 b i b_i bi。题目保证两个序列长度都为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;只要两个点之间有操作, 那么只要两个边有操作, 那么就连边。可见, 只要两个点是联通的, 就能在这两个点身上进行操作。
如上面两种转移操作。
可见, d i s t ( a , b ) ⊕ d i s t ( b , c ) = d i s t ( a , c ) dist(a, b) \oplus dist(b, c) = dist(a, c) dist(a,b)⊕dist(b,c)=dist(a,c)
那么, 就想到了并查集的操作, 每次吧中专点枚举唯根节点即可。
还有判断为一的自环的问题, 用一个数组记录, 足矣!!
#include
#include
#include
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;
}
给定一个1 ~ n 的排列 p i p_i pi, 接下来有m次操作, 操作共两种:
1.交换操作:给定x, 把当前排列的第x个数与第x+1个数交换位置。
2.询问操作:给定k, 请你求出当前排列经过k轮冒泡排序后逆序对的个数。对一长度为n的排列 p i p_i pi进行一轮冒泡排序的伪代码如下:
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数组表示在当前下标位置上, 前面有多少个比他大。
设当前交换的数为 a x a_x ax, , b x b_x bx为当前这个位置前有几个比它大的数。
当 a x < a x + 1 a_x \lt a_{x+1} ax<ax+1时, 那么交换后初始逆序对个数会加一, 同时, 在x+1的位置上有多了个ax+1所以 b x + 1 b_{x+1} bx+1也会加1。但是当 b x b_x bx为0时, 也就是x前没有数比 a x a_x ax大时, 那么这个逆序对就是无效的, 因为下一轮冒泡接着就交换回去了。
当 a x > a x + 1 a_x \gt a_{x+1} ax>ax+1时, b x b_x bx - 1.当 b x + 1 b_{x+1} bx+1为0时, 就失效了。
差分思想查询:前缀求和。
#include
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;
}
给定一个长度为n的正整数序列 a i a_i ai,下标从1开始编号。我们将该序列视为一个首尾相连的环, 对于下标为 i , j ( i ≤ j ) i,j(i \leq j) i,j(i≤j)的两个数 a i , a j a_i, a_j ai,aj, 他们的距离为 m i n ( j − i , i + n − j ) min(j-i, i+n-j) min(j−i,i+n−j)。
现在再给定m个整数 k 1 , k 2 . . . . . . , k m k_1, k_2......, k_m k1,k2......,km, 对每个 k i ( i = 1 , 2 , . . . . , m ) k_i(i = 1, 2,...., m) ki(i=1,2,....,m),你需要将上面的序列 a i a_i ai重新排列, 使得换上任意两个距离为 k i k_i ki的数字的乘积之和最大。
显然, 每个数距离为0的点就是他自己, 所以, 答案就为每个数的平方和。
看样例, 我们的6个数中要使相邻两个数的乘积之和最大, 那么先把6放进去。然后再放与他相邻最大的5,4, 再放最大的2,3, 但大贴大, 小贴小。这样很显然是正确的, 因为设5, 4, 为一组, 设成a, b; 3,2为一组, 设成c, d;那么我们这一种方案就是 a c + b d ac+bd ac+bd
另一种就是 a d + b c ad+bc ad+bc, 显然, 两者一减, 前一种方案大。
把他分成两个环, 分别解决。
环的长度为n/gcd(n, k)
长度一样答案一样, 记得记忆化。
#include
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;
}