Codeforces Round #772 (Div. 2)简训
导语
两个,真菜
涉及的知识点
二分图,思维,数学,线性dp
链接:Codeforces Round #772 (Div. 2)
题目
A Min Or Sum
题目大意:给出一个有n个元素的序列a,选择两个下标i,j,将 a i a_i ai替换成 x x x, a j a_j aj替换成 y y y,但是 a i a_i ai或上 a j a_j aj= x x x或上 y y y,求出无限次操作后能得到的最小序列和
思路:贪心的考虑,直接将 x x x变成 a i ∣ a j a_i|a_j ai∣aj, y y y变成0,然后接着构造即可,最后结果等价于求所有数的或和
代码
#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=2e5+5;
int n,m,t,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
int res=0;
while(n--) {//求或和
int x;
cin >>x;
res|=x;
}
cout <<res<<endl;
}
return 0;
}
B Avoid Local Maximums
题目大意:给出有n个整数的序列a,每个元素 [ 1 , 1 0 9 ] [1,10^9] [1,109],现在每次可以选择将序列中的任意一个元素替换成范围内的一个数,输出最少需要几次操作才能使得序列中不存在大于自己相邻数的元素,并输出构造的最后的序列
思路:考虑贪心,遍历一遍就能处理完是最理想的情况,一开始的思路是对大于相邻数的元素直接替换成相邻数的最大值,但是不能这样,因为可能会对已经经过的下标产生干扰,正确做法是当发现当前遍历的元素大于相邻数时,需要替换的是右边的邻居,因为这样的就不会对已经构造好的左边产生干扰
代码
#include <bits/stdc++.h>
#define int long long
#define INF 1e9
using namespace std;
const int maxn=2e5+5;
int n,m,t,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
int ans=0;
for(int i=1; i<=n; i++)cin >>a[i];
for(int i=2; i<=n-1; i++)
if(a[i]>a[i-1]&&a[i]>a[i+1])ans++,a[i+1]=max(a[i],a[i+2]);
//如果大于左邻右舍,需要更改的是右边
cout <<ans<<endl;
for(int i=1; i<=n; i++)
cout <<a[i]<<" ";
cout <<endl;
}
return 0;
}
C Differential Sorting
题目大意:给出有n个整数序列a,定义操作如下:选择三个下标 x , y , z ( x < y < z ) x,y,z(x<y<z) x,y,z(x<y<z),将 a x a_x ax替换成 a y − a z a_y-a_z ay−az,操作之后 ∣ a x ∣ ≤ 1 0 18 |a_x|\le10^{18} ∣ax∣≤1018,在操作次数不超过 n n n的前提下使得最后的序列为非递减序列,如果存在多种方案,输出操作数与对应操作,否则输出-1
思路:一开始思考的时候忽略了一个关键条件: x < y < z x<y<z x<y<z,这意味着,每个元素只能选择在下标之后的两个不同元素,那么显然,第n-1和n个元素是没办法被替换的,因此,如果第n-1个元素大于第n个元素,则一定无法保持整个序列非递减,接下来就是对第n个元素进行讨论(因为可以用第n-1个元素和第n个元素来对前n-2个元素统一赋成一个值),如果第n个元素非负(保证第n-1小于它),那么第n-1个元素-第n个元素必然为负数且小于第n-1个元素,可以构造,如果第n个元素为负,那么其余元素必须为负,否则就会有一个元素大于第n个元素了,并且,如果这n个负数不是升序排序的话,那么任意一次替换操作必然会产生一个数比其后面的数大
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int n,m,t,p,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
for(int i=1; i<=n; i++)cin >>a[i];
if(a[n]<a[n-1]) {//最后一个不满足条件
cout <<-1<<endl;
continue;
}
if(a[n]>=0) {//一定可以构造
cout <<n-2<<endl;
for(int i=1; i<=n-2; i++)
cout <<i<<" "<<n-1<<" "<<n<<endl;
continue;
}
if(is_sorted(a+1,a+1+n))cout <<0<<endl;//判断是否有序
else cout <<-1<<endl;
}
return 0;
}
D Infinite Set
题目大意:给出有n个不同整数的序列a,定义一个无穷元素集 S S S包括了所有满足下列一种条件的整数 x x x
- x = a i , 1 ≤ i ≤ n x=a_i,1\le i\le n x=ai,1≤i≤n
- x = 2 y + 1 , y ∈ S x=2y+1,y\in S x=2y+1,y∈S
- x = 4 y , y ∈ S x=4y,y\in S x=4y,y∈S
输出 S S S集合内所有严格小于 2 p 2^p 2p的元素个数
思路:对于一个正整数 x x x,定义一个函数 f ( x ) = k ( 2 k ≤ x < 2 k + 1 ) f(x)=k(2^k\le x\lt2^{k+1}) f(x)=k(2k≤x<2k+1),那么可以得到 f ( 2 x + 1 ) = f ( x ) + 1 , f ( 4 x ) = f ( x ) + 2 f(2x+1)=f(x)+1,f(4x)=f(x)+2 f(2x+1)=f(x)+1,f(4x)=f(x)+2
首先从一个简单的情况开始,假设 n = 1 , a 1 = 1 n=1,a_1=1 n=1,a1=1,定义 d p i dp_i dpi为满足 x ∈ S , f ( x ) = i x\in S,f(x)=i x∈S,f(x)=i的 x x x的个数,根据 f ( x ) f(x) f(x)之间的转换关系,那么可以得到递推式: d p i = d p i − 1 + d p i − 2 dp_i=dp_{i-1}+dp_{i-2} dpi=dpi−1+dpi−2,最后可以得到答案为: ∑ i = 0 p − 1 d p i \sum_{i=0}^{p-1}dp_i ∑i=0p−1dpi
现在来讨论一般情况,为了避免重复计算,定义无用数 a i a_i ai:如果存在一个下标 j j j满足 a i = 2 a j + 1 o r a i = 4 a j a_i=2a_j+1 \quad or\quad a_i=4a_j ai=2aj+1orai=4aj,那么 a i a_i ai为无用数,也就是说,如果一开始给出的序列 a a a中存在这样的一个 a i a_i ai,由于其可以被先前的项推导出来,所以可以不考虑它的贡献
定义 g ( i ) g(i) g(i)为满足 f ( a j ) = i f(a_j)=i f(aj)=i的下标 j j j的数量,那么更一般的情况就可以得到递推式: d p i = d p i − 1 + d p i − 2 + g ( i ) dp_i=dp_{i-1}+dp_{i-2}+g(i) dpi=dpi−1+dpi−2+g(i),这里加上 g ( x ) g(x) g(x)是因为有些 a j a_j aj是无法通过前项推得(不是从1开始的),所以需要加上
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
const int mod=1e9+7;
int n,m,t,p,a[maxn],ans[maxn],dp[maxn],res;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>n>>p;
for(int i=1; i<=n; i++)
cin >>a[i];
sort(a+1,a+1+n);
set<int>use;
for(int i=1; i<=n; i++) {
int x=a[i];
bool flag=0;
while(x>0) {
if(use.count(x))
flag=1;
if(x&1)x>>=1;//是奇数,代表是2x+1来的
else if(x&3)break;//4k+2无法生成
else x>>=2;//代表是4x来的
}
if(!flag)use.insert(a[i]);//判断是否有用数字
}
for(int x:use)
ans[(int)log2(x)]++;//记录初始集合中有用数字对应的贡献
for(int i=0; i<p; i++) {
if(i<30)dp[i]=ans[i];//统计初始集合中的贡献
if(i>=1)//公式
dp[i]=(dp[i]+dp[i-1])%mod;
if(i>=2)//公式
dp[i]=(dp[i]+dp[i-2])%mod;
res=(res+dp[i])%mod;
}
cout <<res<<endl;
return 0;
}
E Cars
题目大意:
x
x
x轴上有
n
n
n辆汽车,每一辆车初始时位于一个整点并且没有同一辆车位于同一点,每一辆车初始朝向要么是左,要么是右,并且它们可以随时以任何恒定的正速度沿该方向移动,用一个字符和一个整数来描述每一辆车的状态:
o
r
i
i
,
x
i
ori_i,x_i
orii,xi分别对应初始方向和坐标
如果两辆车无论速度是多少,它们永远都不会到达同一点,定义为无用的,也就是无论速度多少在任何时候这两辆车不会共享相同的坐标
如果两辆车无论速度是多少,它们永远都会到达同一点,定义为无用的,也就是无论速度多少在某一时刻这两辆车会共享相同的坐标
现在只有汽车之间的关系,尝试构造出一种包括所有汽车的初始朝向和位置的方案使得满足这些关系
某一时刻,如果两辆车共享相同坐标,之后它们将继续沿着各自的方向移动
思路:首先确定,满足上述两个条件的情况不存在两辆车同向而行,对于两个一定相遇的车,如果同向而行,其中远车速度过大的话就无法相遇了,对于两个一定不相遇的车,近车速度过大的话就可以相遇了,所以对于给出关系的两辆车要么相向,要么异向
那么对于拥有给定关系的两辆车就一定有相反方向,所以可以对存在关系的车建图, 然后进行二分图染色判断是否有颜色相同的邻接点,存在则无解,接下来根据关系建有向边,如果一定相遇,前者坐标小于后者,否则相反,建完有向边后再跑一次拓扑排序判断是否存在环,存在则无解,否则可以直接将拓扑序作为坐标
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200001;
struct edge {
int type, u, v;
};
vector <int> adj[N];
int col[N], topo[N];
void dfs(int v) {//染色
for (int u : adj[v]) if (col[u] == -1) {
col[u] = col[v] ^ 1;
dfs(u);
}
}
bool BipartiteColoring(int n) {
for (int i = 1; i <= n; ++i)
col[i] = -1;
for (int i = 1; i <= n; ++i) if (col[i] == -1) {
col[i] = 0;
dfs(i);
}
for (int i = 1; i <= n; ++i) for (int j : adj[i]) {//存在邻接点染色相同
if (col[i] == col[j]) {
return false;
}
}
return true;
}
bool TopologicalSort(int n) {//拓扑排序
vector <int> in(n + 1, 0);
for (int i = 1; i <= n; ++i) for (int j : adj[i]) {//统计入度
in[j]++;
}
queue <int> q;
for (int i = 1; i <= n; ++i) if (in[i] == 0) {//加入初始节点
q.push(i);
}
int ord = 0;
while (!q.empty()) {
int v = q.front();
q.pop();
topo[v] = ord++;//拓扑序作为坐标
for (int u : adj[v]) {
in[u]--;
if (in[u] == 0) {
q.push(u);
}
}
}
return ord == n;
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
vector <edge> a(m);
for (int i = 0; i < m; ++i) {//建图
cin >> a[i].type >> a[i].u >> a[i].v;
adj[a[i].u].push_back(a[i].v);
adj[a[i].v].push_back(a[i].u);
}
if (!BipartiteColoring(n)) {//判断是否能二分图染色
cout << "NO" << endl;
return 0;
}
for (int i = 1; i <= n; ++i) {
adj[i].clear();
}
for (edge e : a) {
if (col[e.u] == 1)
swap(e.u, e.v);
if (e.type == 1) {//根据情况判断有向边方向
adj[e.u].push_back(e.v);
} else {
adj[e.v].push_back(e.u);
}
}
if (!TopologicalSort(n)) {//判断是否有环
cout << "NO" << endl;
return 0;
}
cout << "YES" << endl;
for (int i = 1; i <= n; ++i) {
cout << (col[i] == 0 ? "L " : "R ") << topo[i] << endl;
}
return 0;
}