[构造题选讲]
构造,人类智慧题。
使用数学公式直接求出构造方法。可能需要一些数学功底。
归纳法。先考虑如何构造小的情况,再通过小的情况构造大的情况。
考虑特殊情况。比如要求构造一个特定的图,那么可以自己添加条件限制范围,比如特定的二分图、特定的树、特定的链等等。一个常见的条件就是对称性。构造具有数学美的答案!
CF1438D Powerful Ksenia
考虑这种三位操作的,我们一般可以考虑是否有连续的操作。
那么我们发现如果当\(n\)为奇数则一定有一种可行的操作。
即将序列变为\((a1,a1,a2,a2,a3,a3,a3)\)类,再操作即可。
当\(n\)为偶数,我们发现一次操作不会改变从左到右整个序列的异或值,则当整个序列异或为\(0\)时,我们只要对前\(n - 1\)个数操作即可。
// Problem: CF1438D Powerful Ksenia
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1438D
// Memory Limit: 250 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
#include<cstdio>
#define ll long long
#define N 100005
ll n,a[N];
int main(){
scanf("%lld",&n);
for(int i = 1;i <= n;++i)
scanf("%lld",&a[i]);
if(n % 2 == 1){
std::cout<<"YES"<<std::endl;
std::cout<<n - 1<<std::endl;
for(int i = 1;i <= n - 2;i += 2)
std::cout<<i<<" "<<i + 1<<" "<<i + 2<<std::endl;
for(int i = n - 2;i >= 1;i -= 2)
std::cout<<i<<" "<<i + 1<<" "<<i + 2<<std::endl;
}else{
ll x = 0;
for(int i = 1;i <= n;++i)
x = x ^ a[i];
if(x != 0)
std::cout<<"NO"<<std::endl;
else{
n -= 1;
std::cout<<"YES"<<std::endl;
std::cout<<n - 1<<std::endl;
for(int i = 1;i <= n - 2;i += 2)
std::cout<<i<<" "<<i + 1<<" "<<i + 2<<std::endl;
for(int i = n - 2;i >= 1;i -= 2)
std::cout<<i<<" "<<i + 1<<" "<<i + 2<<std::endl;
}
}
}
AT5759 ThREE
考虑在树上黑白染色,则发现距离为3的都是不同颜色的点。
那么考虑到\(x\ mod\ 3 = 0\)的点是放哪都行的。
那么我们只要让另外两种分属不同的颜色的点的就行了。
分类讨论:\(x < \frac{n}{3}\)与\(x,y > \frac{n}{3}\)
// Problem: AT5759 ThREE
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT5759
// Memory Limit: 1000 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
#include<cstdio>
#define ll long long
#define N 400005
ll n;
struct P{
int to,next;
}e[N << 1];
ll head[N],cnt;
inline void add(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
ll co1[N],co2[N],cnt1,cnt2;
ll ca[N];
inline void dfs(int u,int fa){
ca[u] = ca[fa] ^ 1;
if(ca[u] == 0){
co1[++cnt1] = u;
}else {
co2[++cnt2] = u;
}
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
if(v == fa)continue;
dfs(v,u);
}
}
ll ans[N];
ll c1 = 1,c2 = 2,c3 = 3;
inline ll find1(){return c1 > n ? 0 : c1;}
inline ll find2(){return c2 > n ? 0 : c2;}
inline ll find3(){return c3 > n ? 0 : c3;}
int main(){
scanf("%lld",&n);
for(int i = 1;i < n;++i){
ll x,y;
scanf("%lld%lld",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
if(cnt1 > cnt2)std::swap(co1,co2),std::swap(cnt1,cnt2);
if(cnt1 < n / 3){
for(int i = 1;i <= cnt1;++i){
ans[co1[i]] = find3();
c3 += 3;
}
for(int i = 1;i <= cnt2;++i){
if(find1()){
ans[co2[i]] = find1();
c1 += 3;
}else{
if(find2()){
ans[co2[i]] = find2();
c2 += 3;
}else{
ans[co2[i]] = find3();
c3 += 3;
}
}
}
}else{
for(int i = 1;i <= cnt1;++i){
if(find1()){
ans[co1[i]] = find1();
c1 += 3;
}else{
ans[co1[i]] = find3();
c3 += 3;
}
}
for(int i = 1;i <= cnt2;++i){
if(find2()){
ans[co2[i]] = find2();
c2 += 3;
}else{
ans[co2[i]] = find3();
c3 += 3;
}
}
}
for(int i = 1;i <= n;++i)
std::cout<<ans[i]<<" ";
}
【XR-2】伤痕
考虑一组不强连通的四个点有3种可能:
1.从一个点向另外三个点各连一条有向边
2.在不满足第一种的条件下,三个点往一个点连有向边。
- A 与 B、C 与 D 之前都是无向边,但从 A 向 CD 各连一条有向边,从 B 向 CD 各连一条有向边。
记点\(i\)向其他点连的有向边有\(S_i\)条。因为\(n\)为奇数,有:
\(\sum_{i = 1}^{n}S_i = \frac{n * (n - 1)}{2} - n = n * \frac{n - 3}{2}\)。
又因为\(C^{3}_{x}\)是凸函数。
所以\(X = \sum_i^{n}C^{3}_{s_i} > n * C^3_{\frac{n - 3}{2}}\)
所以我们只要构造一个只有这么多的第一种可能,没有第二种可能,没有第三种可能的情况。
将$ n $个点放在一个圆内接正 n 边形的顶点上,所有最长的对角线为无向边,每个点都向顺时针接下来的\frac{n - 3}{2} 个点连一条有向边。
答案为\(C^4_n - n * C^3_{\frac{n - 3}{2}}\)
CF468C Hack it!
考虑\(f(x) = y,f(x + 1e18) = y + 1\)。
那么考虑\(\sum_0^{1e18 - 1} f(i) = p(mod\ a)\)
那么\(\sum_l^{1e18 + l - 1} = p + l(mod\ a)\)
那么我们只要求出\(p\)是多少就好了。
注意到\(\sum_0^{1e18 - 1} = 45 \times 1e17 + 10 \times \sum_0^{1e17 - 1}f(i)\)
所以\(\sum_0^{1e18 - 1} = 18 * 45 * 1e17\)
[NOIP2020] 移球游戏
先考虑两个颜色的时候怎么做。
然后进行分治处理。
// Problem: P7115 [NOIP2020] 移球游戏
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7115
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
#include<cstdio>
#define ll long long
#define N 55
#define M 405
#define K 820005
int a[N][M],top[N],n,m,ans[K][2],tot;
bool f[N];
inline void pour(int x,int y){
ans[++tot][0] = x,ans[tot][1] = y;
a[y][++top[y]] = a[x][top[x]--];
}
inline void del1(int x,int y,int k){//还原x
int s = 0;
for(int i = 1;i <= m;++i)
s += (a[x][i] <= k);
for(int i = 1;i <= s;++i)
pour(y,n + 1);
while(top[x])a[x][top[x]] <= k ? pour(x,y) : pour(x,n + 1);
for(int i = 1;i <= s;++i)pour(y,x);
for(int i = 1;i <= m - s;++i)pour(n + 1,x);
for(int i = 1;i <= m - s;++i)pour(y,n + 1);
for(int i = 1;i <= m - s;++i)pour(x,y);
while(top[n + 1]){
if(top[x] == m || a[n + 1][top[n + 1]] > k)
pour(n + 1,y);
else
pour(n + 1,x);
}
}
inline void del2(int x,int y,int k){
int s = 0;
for(int i = 1;i <= m;++i)
s += (a[x][i] > k);
for(int i = 1;i <= s;++i)
pour(y,n + 1);
while(top[x])a[x][top[x]] > k ? pour(x,y) : pour(x,n + 1);
for(int i = 1;i <= s;++i)pour(y,x);
for(int i = 1;i <= m - s;++i)pour(n + 1,x);
for(int i = 1;i <= m - s;++i)pour(y,n + 1);
for(int i = 1;i <= m - s;++i)pour(x,y);
while(top[n + 1]){
if(top[x] == m || a[n + 1][top[n + 1]] <= k)
pour(n + 1,y);
else
pour(n + 1,x);
}
}
#define mid ((l + r) >> 1)
inline void solve(int l,int r){
if(l == r)return;
int li = l,ri = mid + 1;
while(li <= mid && ri <= r){
int s = 0;
for(int i = 1;i <= m;++i)
s += (a[li][i] <= mid);
for(int i = 1;i <= m;++i)
s += (a[ri][i] <= mid);
if(s >= m){
del1(li,ri,mid);
li ++ ;
}else{
del2(ri,li,mid);
ri ++ ;
}
}
solve(l,mid),solve(mid + 1,r);
}
int main(){
scanf("%lld%lld",&n,&m);
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j){
scanf("%lld",&a[i][++top[i]]);
}
solve(1,n);
std::cout<<tot<<std::endl;
for(int i = 1;i <= tot;++i)
std::cout<<ans[i][0]<<" "<<ans[i][1]<<std::endl;;
}