AtCoder Regular 120 A B (思维 C 思维+求逆序数(树状数组、归并排序

———题解————

A.Max add
两个前缀和

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
ll n, m, ans, x, k;
ll a[maxn];
ll f[maxn];
ll g[maxn];
int main()
{
	cin >> n;
	for(int i = 1; i <= n; ++i)	
	scanf("%lld", &a[i]), f[i] = f[i-1] + a[i], g[i] = f[i] + g[i-1];
	ll Max = a[1];
	for(int i = 1; i <= n; ++i)
	{
		Max = max(Max, a[i]);
		printf("%lld\n", Max*i+g[i]);
	}
	return 0;
}

B - Uniformly Distributed
思维 找规律算贡献

规律:从右上到左下的副对角线上必须全为蓝色或者全为红色
这样一条对角线上的格子的坐标特征:i+j 相等
对于一条要么全是红格子或者蓝格子的副对角线,其对答案无贡献(有一个格子红色就相当于全红,一个是蓝色就相当于全蓝);对于一条全空的副对角线,其对答案的贡献为 2 (两种涂色方案);对于既有蓝色又有红色的副对角线,直接break输出0

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1000 + 9;
const int mod = 998244353;
ll n, m, ans, x, k;
ll r[maxn*2], b[maxn*2];
char s[maxn][maxn];
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i)	
		scanf("%s",s[i]+1);
	ll ans = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			if(s[i][j]=='R') ++r[i+j];
			else if(s[i][j]=='B') ++b[i+j];
	for(int i = 2; i <= n+m; ++i)
		if(!r[i] && !b[i]) ans = (ans*2ll)%mod;
		else if(b[i] && r[i]){
			ans = 0;break;
		}
	printf("%lld\n", ans);
	return 0;
}

C - Swaps 2
先考虑解的存在性
首先根据所给操作可以发现一个数往左走增大,往右走减小。设 A i A_i Ai 新位置是 D i D_i Di,则必然有 B D i = A i + i − D i B_{D_i}=A_i+i-D_i BDi=Ai+iDi,移项得到等式 B D i + D i = A i + i B_{D_i}+D_i=A_i+i BDi+Di=Ai+i
可以发现两个等式相似,只需建立两个新序列 A i + i {A_i+i} Ai+i B i + i {B_i+i} Bi+i,然后排序判断是否能 一 一 对应,就可以判断是否存在解了。
然后考虑解的最优性
若按照值为第一关键字排序,原下标为第二关键字排序,则我们不难证明,此时一一对应的结果是最优的,这样 D i D_i Di 就找到了。
新开一个数组建立两个序列下标一一对应的关系

最后其实就是求解新建数组的逆序数
为什么求逆序数就是答案,参考火柴排队这道题目
树状数组求逆序数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
ll n, m, sum[MAXN], A[MAXN];

struct node
{
	int val, id;
	bool operator<(const node &b)const{
		return val == b.val ? id < b.id : val < b.val;
	}
} a[MAXN], b[MAXN];

ll getsum(int i)
{
	ll ans = 0;while(i > 0) ans += sum[i], i -= i & (-i);return ans;
}
void update(int i, ll k)
{
	while(i <= n) sum[i] += k, i += i & (-i); 
}

int main()
{
    //ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i].val),a[i].val+=i,a[i].id = i;
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i].val),b[i].val+=i,b[i].id = i;
    sort(a + 1, a + 1 + n);sort(b + 1, b + 1 + n);
    for(int i = 1; i <= n; ++i)	
    	if(a[i].val != b[i].val) return printf("-1\n"), 0;
    	else A[b[i].id] = a[i].id;// 一 一 对 应
    ll ans = 0;
     for(int i = 1; i <= n; i++)// 树状数组求逆序数
        ans += A[i]-1 - getsum(A[i] - 1), update(A[i], 1ll);
    printf("%lld\n", ans);
    return 0;
}

解法二:
归并排序求逆序对
其实就是 CDQ分治 的思路
code:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
ll B[MAXN], ans;
struct node
{
	int val, id;
	bool operator<(const node &b)const{
		return val == b.val ? id < b.id : val < b.val;
	}
} a[MAXN], b[MAXN];

LL ms(int l, int r)// 板子
{
    if(l >= r) return 0;
    int m = (l + r) >> 1;//二分 
    LL t1 = ms(l, m), t2 = ms(m + 1, r);//获得前半部分 和 后半部分 的逆序对个数 
    LL t3 = 0;//两边归并的逆序对个数 
    int i = l, j = m + 1, p = l;//前半部分起点、后半部分起点、升序排序数组指针 
    while(i <= m && j <= r)//进行排序操作 
	{
        if(A[i] > A[j])//如果前部分的某个元素大于后半部分某个元素 
		{
            B[p++] = A[j++];//升序排序 
            t3 += m - i + 1;//因为l到m区间内中:i后的元素都比a[i]大,所以也比a[j]大,所以逆序对有m-i+1(包括自己)个 
        }
        else B[p++] = A[i++];//升序排序 
    }
    //完成剩下的归并操作 
    while(i <= m) B[p++] = A[i++];
    while(j <= r) B[p++] = A[j++];
    for(int i = l; i <= r; i++) A[i] = B[i];//将排序的值返回给a 
    return t1 + t2 + t3;//返回 
}
int main()
{
    //ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i].val),a[i].val+=i,a[i].id = i;
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i].val),b[i].val+=i,b[i].id = i;
    sort(a+1,a+1+n);sort(b+1,b+1+n);
    for(int i = 1; i <= n; ++i)	
    	if(a[i].val != b[i].val) return printf("-1\n"), 0;
    	else A[b[i].id] = a[i].id;
	// 到这一步题目分析基本完成,可以用线段树维护下标求答案
	// 也可以用归并排序等方法求解逆序数即为答案
    printf("%lld\n", ms(1, n));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值