A.Direction Change
- 讨论一下每种移动的情况即可。先交替,然后再走多的。
- 走多的过程需要交替在其他方向移动。
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int n,m;
cin >> n >> m;
int r = m - 1; //向右移动
int d = n - 1; //向下移动
if(abs(d-r) <=1)
cout << r + d << endl;
else
{
if(r == 0 || d == 0) cout << -1 << endl;
else
{
int a = 0;
a += min(r,d)*2;
a+=2*(max(r,d) - min(r,d));
if((max(r,d) - min(r,d)) % 2 == 1) a--;
cout << a << endl;
}
}
}
system("pause");
return 0;
}
B.Social Distance
- 从需要最多椅子的人开始,递减安排
- 每多来一个人,前一个安排的人需要用到额外的与其数量对应的空椅子。
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int n,m;
cin >> n >> m; //人的数量 椅子的数量
int a[n];
for(int k = 0;k<n;k++) cin >> a[k];
sort(a,a+n);
m--;
m-=a[n-1]; //空椅子的数量
n--;
if(m<0) cout<<"NO"<<endl;
else
{
bool flag = true;
while(n>=1) //还有人未被安排
{
m-=a[n];
m--;
if(m<0) {flag = false;break;}
n--;
}
if(flag) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
system("pause");
return 0;
}
C.Make it Increasing
- 考察最优解的性质:最优解中一定包含一个0
- 0右侧仅包含加,0左侧仅包含减
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll n;
cin >> n;
ll a[n];
for(int i = 0;i<n;i++) cin >> a[i];
ll ans = LONG_LONG_MAX;
for(int i = 0 ;i < n;i++) //枚举每一位为0的位子
{
ll pre1 = 0;
ll temp = 0;
for(int j = i - 1;j>=0;j--)
{
ll m = (ll)ceil((double)abs((double)pre1 - 1) / a[j]);
temp += m;
pre1 = -m * a[j];
}
ll pre2 = 0;
for(int j = i + 1;j<n;j++)
{
ll m = (ll)ceil(double(pre2 + 1) / a[j]);
temp += m;
pre2 = m * a[j];
}
ans = min(ans,temp);
}
cout << ans << endl;
system("pause");
return 0;
}
D.Optimal Partition(DP优化)
思路(线段树)
- 容易列出dp方程:dp[j],以j号下标结尾的最大和
a = m a x p r e [ j ] − p r e [ i ] > 0 { d p [ i ] + j − i } b = m a x p r e [ j ] − p r e [ i ] < 0 { d p [ i ] + i − j } c = m a x p r e [ j ] = p r e [ i ] { d p [ i ] } d p [ j ] = m a x { a , b , c } a = max_{pre[j] - pre[i] > 0}\{dp[i] + j - i\} \\ b = max_{pre[j] - pre[i] < 0}\{dp[i] + i - j\} \\ c = max_{pre[j] = pre[i]}\{dp[i]\} \\ dp[j] = max\{a,b,c\} a=maxpre[j]−pre[i]>0{dp[i]+j−i}b=maxpre[j]−pre[i]<0{dp[i]+i−j}c=maxpre[j]=pre[i]{dp[i]}dp[j]=max{a,b,c} - 对于每个j需要找到与pre[j]满足一定条件的所有pre[i],并求其对应的最大值。
- 维护某个大小前缀和的dp[i]-i或dp+i或dp[i]的最大值。每次查询小于,大于,等于某个前缀和所有前缀和对应的上述a,b,c的最大值。
- 前缀和取值有限,将前缀和离散化。方法是将其排序。
- 可以使用线段树维护。每次查询或更新某个区间的最值。
线段树
- 复杂度 O ( l o g n ) O(logn) O(logn)-区间的连续性
代码
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
#include<math.h>
typedef long long ll;
using namespace std;
ll d1[9500001];
ll d2[9500001];
ll d3[9500001];
ll a[500001];
ll f[500001];
ll pr[500001];
void build(int s, int t, int p,ll d[]) {
// 对 [s,t] 区间建立线段树,当前根的编号为 p
if (s == t) {
d[p] = -1e18;
return;
}
int m = s + ((t - s) >> 1);
// 移位运算符的优先级小于加减法,所以加上括号
// 如果写成 (s + t) >> 1 可能会超出 int 范围
build(s, m, p * 2,d), build(m + 1, t, p * 2 + 1,d);
// 递归对左右区间建树
d[p] = -1e18;
}
ll query(int l, int r, int s, int t, int p,ll d[]) {
// [l, r] 为查询区间, [s, t] 为当前节点包含的区间, p 为当前节点的编号
// if(r < l) return LONG_LONG_MIN + 1e18;
if (l <= s && t <= r)
return d[p]; // 当前区间为询问区间的子集时直接返回当前区间的和
int m = s + ((t - s) >> 1);
ll ans = LONG_LONG_MIN;
if (l <= m) ans = max(ans,query(l, r, s, m, p * 2,d));
// 如果左儿子代表的区间 [l, m] 与询问区间有交集, 则递归查询左儿子
if (r > m) ans = max(ans,query(l, r, m + 1, t, p * 2 + 1,d));
// 如果右儿子代表的区间 [m + 1, r] 与询问区间有交集, 则递归查询右儿子
return ans;
}
// C++ Version
void update(int index,ll c, int s, int t, int p, ll d[]) {
if (s == t && s== index) {
d[p] = max(d[p],(ll)c);
return;
}
int m = s + ((t - s) >> 1);
if(index <= m) update(index,c,s,m,2*p,d);
else update(index,c,m+1,t,2*p+1,d);
d[p] = max(d[p*2],d[p*2+1]);
}
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int n;
cin >> n;
memset(pr,0,n*sizeof(ll));
memset(f,0,n*sizeof(ll));
vector<ll> pre;
for(int k = 0;k<n;k++)
{
cin >> a[k];
ll pre1;
if(k == 0) pre1= 0 ; else pre1 = pre[k-1];
pre.push_back(pre1 + a[k]);
pr[k] = pre1 + a[k];
}
//cout << pre[n-1] << endl;
sort(pre.begin(),pre.end());
pre.erase(unique(pre.begin(),pre.end()),pre.end());
int n1 = pre.size();
build(1,n1,1,d1);build(1,n1,1,d2); build(1,n1,1,d3);
int w = lower_bound(pre.begin(),pre.end(),pr[0]) - pre.begin() + 1;
if(a[0] < 0) f[0]=-1; else if(a[0] > 0) f[0] = 1; else f[0]=0;
update(w,f[0],1,n1,1,d1);update(w,f[0],1,n1,1,d2); update(w,f[0],1,n1,1,d3);
for(int k = 1;k<n;k++)
{
w = lower_bound(pre.begin(),pre.end(),pr[k]) - pre.begin() + 1;
f[k]=-1e10;
//cout << query(1,w-1,1,n1,1,d1) << endl;
if(w-1>=1) f[k]=query(1,w-1,1,n1,1,d1) + k;
ll a;
if(pr[k]<0) a=-k-1;else if(pr[k]>0) a=k+1; else a=0;
if(w+1<=n1) f[k] = max(query(w+1,n1,1,n1,1,d2)- k,f[k]) ;
f[k] = max(query(w,w,1,n1,1,d3),f[k]);
f[k] = max(a,f[k]);
update(w,f[k]-k,1,n1,1,d1);
update(w,f[k]+k,1,n1,1,d2);
update(w,f[k],1,n1,1,d3);
}
cout << f[n-1] << endl;
}
system("pause");
}
E. Half Queen Cover(GOOD)![在这里插入图片描述](https://img-blog.csdnimg.cn/bc978162a4c4485fae198d4044cadc99.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbnRoMjAwMA==,size_20,color_FFFFFF,t_70,g_se,x_16)
思路(找必要条件 然后构造)
-
假定 k k k个皇后满足题意,这 k k k个皇后至多占据k行,至多占据k列。因此至少 n − k n-k n−k行没有皇后占据,至少 n − k n-k n−k列没有皇后占据。因此至少有 ( n − k ) × ( n − k ) (n - k) \times (n-k) (n−k)×(n−k)格点未被占据。
-
必须要使这k个半皇后覆盖至少这 ( n − k ) × ( n − k ) (n - k) \times (n - k) (n−k)×(n−k)个格点。考察这些格点的第一行,第一列所有格点。每一个只能由一个不同的半皇后得到。因此必须要有
k ≥ ( n − k − 1 ) + ( n − k − 1 ) + 1 k \geq (n - k - 1) + (n - k - 1) + 1 k≥(n−k−1)+(n−k−1)+1
即 k ≥ ⌈ 2 n − 1 3 ⌉ k \geq \lceil \frac{2n - 1}{3} \rceil k≥⌈32n−1⌉
目标转换为构造 k = ( n − k − 1 ) + ( n − k − 1 ) + 1 k =(n - k - 1) + (n - k - 1) + 1 k=(n−k−1)+(n−k−1)+1的方案。
构造思路1(官方)
-
分n的不同情况讨论。
-
当 n = 3 x + 2 n = 3x+2 n=3x+2,则 k = 2 x + 1 k = 2x+1 k=2x+1。放置策略如图所示:
-
当 n = 3 x n = 3x n=3x,则 k = 2 x k = 2x k=2x.先在右下角放置1个,转换为 n = 3 ( x − 1 ) + 2 n = 3(x-1)+2 n=3(x−1)+2, k = 2 ( x − 1 ) + 1 k = 2(x-1) + 1 k=2(x−1)+1的上述问题。
-
当 n = 3 x + 1 n = 3x+1 n=3x+1,则 k = 2 x + 1 k = 2x+1 k=2x+1。现在右下角放置2个。转换为 n = 3 ( x − 1 ) + 2 n = 3(x-1)+2 n=3(x−1)+2, k = 2 ( x − 1 ) + 1 k = 2(x-1) + 1 k=2(x−1)+1的上述问题。
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int n;
cin >> n;
int count = 0;
int n1 = n;
if(n == 1) {cout <<1 << endl;cout << 1 << " " << 1 << endl;return 0;}
while(n%3 !=2)
{
n--;
count ++;
}
int x = (n - 2) / 3;
count += (2*x+1);
cout << count << "\n";
for(int i = n+1;i<=n1;i++)
{
cout << i << " " << i << "\n";
}
//开始的x+1个放在左上角
for(int i = 1;i<=x+1;i++) cout << i << " " << x+2-i<<"\n";
//剩下的x个放在右下角
for(int i = n-x+1;i<=n;i++) cout<<i<<" "<<2*n-x+1 - i << "\n";
//system("pause");
return 0;
}
构造思路2
- 如果能使得 ( n − k ) ∗ ( n − k ) (n-k)*(n-k) (n−k)∗(n−k)的方格点紧靠一起,形成方阵,最有可能使用为了将该方阵第一行,第一列的格点填满而必须添加的皇后将该方阵填满的目的。
- 可以让未摆放皇后的行集中在下侧,未摆放皇后的列集中在右侧。从而形成
(
n
−
k
)
∗
(
n
−
k
)
(n-k)*(n-k)
(n−k)∗(n−k)的方阵。在左上角的
k
×
k
k \times k
k×k方阵中,每条蓝线和绿线上都应摆放一个半皇后,如图所示,显然存在将这些皇后放在不同行,不同列上的摆法(打勾位子)。
- 当 k = ( n − k − 1 ) + ( n − k − 1 ) + 1 k =(n - k - 1) + (n - k - 1) + 1 k=(n−k−1)+(n−k−1)+1不存在整数解时,参照官方解答在右下角摆放后再转换为上述问题。
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int n;
cin >> n;
int count = (int)ceil((double)(2*n - 1) /3);
cout << count << endl;
if(n==1) {cout << 1 << " " << 1 << endl;return 0;}
while((2*n - 1) % 3 != 0)
{
cout << n << " " << n << endl;
n--;
}
int k = (2*n-1)/3;
int j = 1;
for(int i = 1;i<=k;i++)
{
cout << i <<" "<< j << endl;
j+=2;
if(j>k) j=2;
}
system("pause");
return 0;
}
F-Edge Elimination ( V e r y G o o d ) (Very \ Good) (Very Good)
思路
本题是一道很好的构造题。
-
由树形结构的特殊性:将该树进行分层。
-
必要条件(observation1):考察对于单个结点所涉及的边的删除情况。设该结点为 v I v_I vI
- 当 d e g ( v i ) deg(v_i) deg(vi)是奇数。与其相连的第一条边的删除需要其所连接的另一个顶点的度为奇数。与其相连的第二条边的删除需要其所连接的另一个顶点的度为偶数。以此类推
- 当 d e g ( v i ) deg(v_i) deg(vi)是偶数,同理。
-
必要条件(observation2):对于每条边,在删除时刻与其关联的点的度的奇偶都是确定的。 这可以从叶节点出发自底向上为每条边得出在删除时刻其关联的两个顶点的度是奇数(称这样的边为奇边)还是偶数(称这样的边为偶边)。
- 由于树形结构的分层特殊性,且每个结点只有一个parent,才可以这样做。
- 根据当前结点的实际度是奇数还是偶数,observation1,以及当前结点关联的边的奇偶情况。关系如果不能满足observation1的删除条件,则肯定输出NO
-
考虑自底向上,红色结点关联的边存在一个合适的删除顺序,橙色结点关联的边也有一个合适的删除顺序。但是边1的删除顺序可能依赖于边2是否删除,也依赖于红橙结点共同的父节点的边的删除顺序,因为这样才能保证边1满足其关联的边同奇,同偶(具体哪一种前面已经给出了),从而能够删除边1。
-
在给定红色结点的任意一个可行删除顺序时,并不会与边2产生任何的拓扑关系。
-
于是可得出:若将原始图每条边作为一个结点(设为图G),对原始图中每个结点,根据其奇偶度任意安排一种边的删除顺序,并将该顺序体现在G中则G是有向无环图。在G上运行拓扑排序即可。
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <map>
#include <queue>
#include <chrono>
#include <math.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int dfs(vector<array<int,2>> adj[],vector<int> adj_edge[],int n,int c,int f)
{
vector<int> even;
vector<int> odd;
int index = -1;
for(array<int,2> e:adj[c])
{
if(e[1]!=f)
{
int v = dfs(adj,adj_edge,n,e[1],c);
if(v==-1) return -1;
else if(v==1) odd.push_back(e[0]); //是奇边
else even.push_back(e[0]); //0,是偶边
}
else index = e[0];
}
//回退至上一个结点
int o_size = odd.size(); int e_size = even.size();
int ret = -1;
if(adj[c].size()%2==0)
{
if(index!=-1&&o_size == e_size+1) {even.push_back(index);ret=0;} //奇数等于偶数加1,加入奇数边
else if(index!=-1&&e_size==o_size+1) {odd.push_back(index);ret=1;} //偶数等于奇数加1,加入奇数边
else if(index==-1&&e_size==o_size) {ret=1;}
}
else
{
if(index!=-1&&e_size==o_size) {odd.push_back(index);ret=1;} //偶数等于奇数,加入奇数边
else if(index!=-1&&o_size==e_size+2) {even.push_back(index);ret=0;} //奇数比偶数多2,加入偶数边
else if(index ==-1 && o_size == e_size+1) {ret=1;}
if(ret!=-1) {if(even.size()>=1)adj_edge[odd[odd.size()-1]].push_back(even[even.size()-1]);odd.pop_back();}
}
if(ret!=-1)
{
assert(even.size() == odd.size());
for(int i = even.size() - 1;i>=0;i--)
{
adj_edge[even[i]].push_back(odd[i]);
if(i-1>=0) adj_edge[odd[i]].push_back(even[i-1]);
}
}
return ret;
}
int main()
{
int t;
cin >> t;
for(int i =0 ;i<t;i++)
{
int n;
cin >> n;
vector<array<int,2>> adj[n];
vector<int> adj_edge[n-1];
vector<array<int,2>> edge;
for(int k = 0;k<n-1;k++)
{
int u,v;cin >> u >> v;u--,v--;
adj[u].push_back({k,v}); adj[v].push_back({k,u});
edge.push_back({u,v});
}
int v = dfs(adj,adj_edge,n,0,-1);
if(v==-1) cout<<"NO"<<endl;
else //run topological sort
{
cout << "YES" << endl;
queue<int> q;
//遍历邻接表确定入度
int ind[n-1];
memset(ind,0,(n-1)*sizeof(int));
for(int k = 0;k<n-1;k++)
for(int v:adj_edge[k])
ind[v]++;
for(int k = 0;k<n-1;k++) if(ind[k] == 0) {q.push(k);}
while(!q.empty())
{
int u = q.front();
cout << edge[u][0]+1 << " " << edge[u][1]+1 << endl;
q.pop();
for(int v : adj_edge[u])
{
ind[v]--;
if(ind[v] == 0)
{
q.push(v);
}
}
}
}
}
system("pause");
return 0;
}