ABC-211补题报告

思路参考:AtCoder Beginner Contest 221【A - G】 - Kanoon - 博客园 

E - LEQ

类逆序对问题,但是这里是非逆序对,并且要求的是构造数量,还要进行Pow(2,x)的计算

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+5, mod=998244353;
int n, a[N], t[N];
vector<int> v;

int Pow(int a,int b){
    int res=1,base=a; 
    while(b>0){if(b&1) res=res*base%mod; base=base*base%mod; b>>=1;}
    return res;
}
inline int lowbit(int x){return x&(-x);}
void add(int p,int x){while(p<=n){t[p]=(t[p]+x)%mod; p+=lowbit(p);}}
inline int ask(int p){int res=0; while(p) res=(res+t[p])%mod, p-=lowbit(p); return res;}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    //1.输入并离散化
    cin>>n; for(int i=1; i<=n; i++) cin>>a[i], v.push_back(a[i]);
    sort(v.begin(), v.end());
    for(int i=1; i<=n; i++)
        a[i] = lower_bound(v.begin(), v.end(), a[i])-v.begin()+1;
    //2.开始计算答案
    int ans = 0, inv = Pow(2,mod-2);
    for(int i=1; i<=n; i++){
        ans = (ans+Pow(2,i-1)*ask(a[i])%mod)%mod;
        add(a[i],Pow(inv,i)); //加上(1/2)^i
    }
    cout<<ans;
}

F - Diameter set

题意:找出一个最大的点集合,使得集合中所有点之间距离都是树的直径,求选取集合的方案数。

思路:注意到直径是奇数的时候,中心是一个线段,树被分为左右两边,只能左边选一个,右边选一个。直径是偶数的时候,中心是一个点,树可能被分为多个集合,每个集合都可选0个或者1个,但是要排除只有一个集合选了1个,或者都选0个的情况。

学到的技巧:

1.add和mul偷懒函数,自动取模

2.用tag数组进行集合分类,cnt作为tag的桶进行计数,非常巧妙的思路

#include <bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define int long long
const int N = 2e5+10, mod=998244353;
int n, d[N], pre[N], tag[N], cnt[N], ans=1; //d距离,pre前驱,tag标记(标记mid的不同子树),cnt计数数组(tag桶)
vector<int> g[N];

void dfs(int u,int fa){
	for(int v:g[u]){
		if(v==fa) continue;
		d[v] = d[u]+1; pre[v] = u; //记录距离和前驱
		if(fa==0) tag[v]=v; //处理标记
		else tag[v]=tag[u];
		dfs(v,u);
	}
}
inline void init(int s){pre[s]=d[s]=0; dfs(s,0);} //把s当做起点,从s开始跑一遍最长路
inline int mul(int a,int b){return (a*b%mod+mod)%mod;}
inline int add(int a,int b){return ((a+b)%mod+mod)%mod;}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>n; for(int i=1; i<=n-1; i++){
		int x,y; cin>>x>>y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	int a, b, maxL=0;
	init(1);
	for(int i=1; i<=n; i++) if(d[i]>=maxL) {maxL=d[i], a=i;} //找离1最远的点,直径上点之一
	init(a);
	for(int i=1; i<=n; i++) if(d[i]>=maxL) {maxL=d[i], b=i;} //找出另外一个直径上的点,ab就是一条直径
	if(maxL%2){ //如果maxL是奇数,那么中心是一条线段
		int na=0, nb=0; //必是两个子树,一个和a一起,一个和b一起
		for(int i=1; i<=n; i++) if(d[i]==maxL) nb++;
		init(b);
		for(int i=1; i<=n; i++) if(d[i]==maxL) na++;
		cout<<mul(na,nb); //a那边选1个,b那边选1个
	} else { //否则,maxL是偶数,中心是一个点
		int mid = b; //mid是中心点,从b开始倒着找
		while(d[mid]>maxL/2){
			mid=pre[mid];
			if(d[mid]==maxL/2) break;
		}
		init(mid); //从mid开始再dfs一遍,找出所有阵营的点数
		int sum = 0;
		for(int i=1; i<=n; i++) if(d[i]==maxL/2) cnt[tag[i]]++; //桶计数
		for(int v:g[mid]) sum+=cnt[v], ans=mul(ans,cnt[v]+1);
		ans = add(ans, -1*(sum+1));
		cout<<ans;
	}
}

G - Jumping sequence

学到的技巧:

1.bitset可用来优化bool类dp

2.二维拉伸到一维的思路很巧妙,后续的进一步的处理也值得学习:{1,-1} -> {2,0} -> {1,0},变成bool类的问题

#include <bits/stdc++.h>
using namespace std;
const int N = 2005, M = 3.6e6+5;
int n, a, b, d[N], sum;
bitset<M> bit[N];
bool addx[N], addy[N]; //记录每一步是否有add

/* 思路:考虑把二维拉伸成一维,假设只有一个方向轴,每次可以走+di或者-di,
如果既能走到a+b,又能走到a-b,那么说明可以(因为这样就说明能分成两部分,一部分是a,一部分是b),否则不行。
考虑到走+di或者-di可能会到达负坐标,不好处理,所以右移处理,每次额外走+di距离,变成走+2*di或者0,目的地变成a+b+sum和a-b+sum
然后再把走的距离/2,每次走+di或者0,目的地坐标也相应/2(注意这里已经可以排除部分情况,如果/2不是整数,说明肯定到不了)
然后进行bit压缩的dp递推,算出所有可到的地方,包含两个目的地就说明Yes,否则No
重点是还原路径:考虑把之前的坐标变换反向映射一下,具体见下。
*/
int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>n>>a>>b; for(int i=1; i<=n; i++) cin>>d[i], sum+=d[i];
	for(int i:{a+b+sum, a-b+sum}) //检查两个目的地是否符合题意,不符合直接No,结束
		if(i%2 || i/2<0 || i/2>sum) {cout<<"No"; return 0;} //是奇数,或者越界都不行
	int x = (a+b+sum)/2, y=(a-b+sum)/2; //两个目的地,必须都能到达
	bit[0][0] = 1; //起点init
	for(int i=1; i<=n; i++)
		bit[i] = bit[i-1]|(bit[i-1]<<d[i]); //可以不动,也可以动
	if(!bit[n][x] || !bit[n][y]) {cout<<"No"; return 0;} //其中一个到不了,结束
	cout<<"Yes"<<'\n'; //否则就一定能到达
	for(int i=n; i>=1; i--){ //原路返回找路径
		if(!bit[i-1][x]) {addx[i]=1; x-=d[i];} //i和i+1位置不同,说明i的时候动了,现在原路返回
		if(!bit[i-1][y]) {addy[i]=1; y-=d[i];}
	}
	for(int i=1; i<=n; i++){ //遍历两个add数组,映射得到原路径
		cout<<(addx[i]? (addy[i]? 'R':'U') : (addy[i]? 'D':'L'));
	}
}
/* 这里简述一下映射关系:
				不处理          方向变形              右移,除以2
	方向		(a,b)	--->   (a+b,a-b)   --->  ((a+b+sum)/2, (a-b+sum)/2)
	右          (1,0)            (1,1)                 (1,1)
	左          (-1,0)          (-1,-1)                (0,0)
	上          (0,1)            (1,-1)                (1,0)
	下          (0,-1)           (-1,1)                (0,1)
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值