Codeforces Round #474 (Div. 1 + Div. 2) - C, F

https://codeforces.com/contest/960

F. Pathwalks

题意
给定 n 个点,m 条边的有向图,每条边有权值 w i w_i wi
找出一条路径(可能经过某个点若干次),满足路径中所有边按照输入给边的顺序相连,并且权值严格上升。
在所有满足的路径中,找到边数最多的一条,输出其边数。

( 1   ≤   n   ≤   1 0 5 ,   1   ≤   m   ≤   1 0 5 ,   0   ≤   w i   ≤   1 0 5 ) (1 ≤ n ≤ 10^5,\ 1 ≤ m ≤ 10^5,\ 0 ≤ wi ≤ 10^5) (1 n 105, 1 m 105, 0 wi 105)

思路
因为路径中所有边按照输入顺序相连,权值严格上升,所以每输入一条边,就可以转移得到以该边结尾的所有路径中最长的长度。
对于给出的边 x ,   y ,   w x,\ y,\ w x, y, w,找所有指向节点 x 的边,用这些边中权值小于 w 的来转移。

用 f[i] 表示,以边 i 结尾的最长满足路径的长度。

那么也就是,找到指向节点 x 的所有权值小于 w 的边,用这些边的 f[i] 最大值转移。

但是遍历所有指向 x 的边找权值小于 w 的 f[i] 来转移复杂度太高,当有 1e5 个边相互指向两个点时,每给出一个边就要遍历其余所有边,复杂度是 n^2 的。需要想办法优化。

可以看出,每次只需要权值小于 w 的边中,最大的 f[i] 来转移,可以对于每个节点建立一个树状数组,将所有指向该点的边的权值 w 作为下标,f[i] 作为对应值。每次找 [1, w-1] 权值中 f[i] 的最大值,树状数组求区间最大值,O(logn)。

但是注意权值 wi 可能为 0,也就是说树状数组中下标 0 的位置也有对应值,但是在插入和询问的时候,无法处理 0 的情况,所以需要搞个偏移量,将所有边的权值都+1。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
map<int, int> c[N];

int lbit(int x){
	return x & -x;
}

void update(int k, int x, int y)
{
	for(int i=x;i<=100000;i+=lbit(i)){
		c[k][i] = max(c[k][i], y);
	}
}

int query(int k, int x)
{
	int maxa = 0;
	for(int i=x;i>=1;i-=lbit(i)){
		maxa = max(maxa, c[k][i]);
	}
	return maxa;
}

signed main(){
	Ios;
	cin >> n >> m;
	
	int ans = 0;
	while(m--)
	{
		int x, y, z; cin >> x >> y >> z;
		z ++;
		
		int maxa = query(x, z-1);
		
		update(y, z, maxa+1);
		
		ans = max(ans, maxa + 1);
	}
	cout << ans;
	
	return 0;
}

妙用树状数组!

其实当看到权值大小只有 1e5 的时候就应该看到端倪,处理方式如果和权值没关系的话,完全可以开到 1e9,而这题只有 1e5,那就要想是不是在数值范围上建立树状数组!


C. Subsequence Counting

题意
对于一个长度为 n 的数列来说,其子序列有 2 n − 1 2^n-1 2n1 个。
现在有一个数列,经过下面操作筛选过后只有 X 个子序列:

  • 对于一个子序列来说,如果序列中元素最大值 - 元素最小值 ≥ d,那么该子序列将会被删掉。

给出 X 和 d,构造一个满足的数列,输出长度 n 和各元素 ai。

1   ≤   X ,   d   ≤   1 0 9 1 ≤ X, d ≤ 10^9 1 X,d 109
1   ≤   n   ≤   1 0 4 , 1   ≤   a i   <   1 0 18 1 ≤ n ≤ 10^4, 1 ≤ ai < 10^{18} 1 n 104,1 ai< 1018

思路
想特殊情况!!

对于一段数列 1 1 1 1 d+1 d+1 d+1 d+1 来说,其前半段和后边段不会产生合法子序列,因为一旦 1 和 1+d 同时存在,这个子序列就会被删掉。所以说前后两段互不影响。
所以,其左右两段产生的子序列的个数总和就是整个数列的子序列个数。

那么,整个序列的就可以这样构造:1 1 ... 1, 1+d 1+d ... 1+d, 1+2d 1+2d ... 1+2d, ...,将其子序列数二进制拼凑成 X 即可。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	Ios;
	int x, d; cin >> x >> d;
	
	bitset<30> f(x);
	
	int t = 0;
	vector<int> v;
	for(int i=0;i<30;i++)
	{
		if(!f[i]) continue;
		int cnt = 1<<i;
		for(int j=1;j<=i;j++) v.push_back(t*d + 1);
		t ++;
		v.push_back(t*d + 1), t ++;
	}
	cout << v.size() << endl;
	for(int x : v) cout << x << " ";
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值