Editorial for ARC121
T1. 2nd Greatest Distance
发现 \(x,y\) 实际上是独立的(这句话有点问题),所以我们将所有 \(x,y\) 分别排序之后,次大值只有可能在 \(x_n-x_1,x_{n}-x_2,x_{n-1}-x_1,y_{n}-y_1,y_n-y_2,y_{n-1}-y_1\) 中出现,但是需要注意,不能让一对点同时提供 \(x\) 和 \(y\),即需要将来自同一个点对的 \(x,y\) 去重,他们只能贡献一次。
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int maxn=2e5;
pii x[maxn+5], y[maxn+5];
int n;
vector< pair<int, pii> >ans;
inline int comp(pii x, pii y){
return (x.fi==y.fi && x.se==y.se) || (x.fi==y.se && x.se==y.fi);
}
signed main(){
n=readin(1);
rep(i, 1, n) x[i]=mp(readin(1), i), y[i]=mp(readin(1), i);
sort(x+1, x+n+1);
sort(y+1, y+n+1);
ans.push_back(mp(x[n].fi-x[1].fi, mp(x[n].se, x[1].se)));
ans.push_back(mp(x[n].fi-x[2].fi, mp(x[n].se, x[2].se)));
ans.push_back(mp(x[n-1].fi-x[1].fi, mp(x[n-1].se, x[1].se)));
ans.push_back(mp(y[n].fi-y[1].fi, mp(y[n].se, y[1].se)));
ans.push_back(mp(y[n].fi-y[2].fi, mp(y[n].se, y[2].se)));
ans.push_back(mp(y[n-1].fi-y[1].fi, mp(y[n-1].se, y[1].se)));
sort(ans.begin(), ans.end());
pii cur=ans.back().se;
// printf("ans.back() == (%d, %d, %d)\n", ans.back().fi, ans.back().se.fi, ans.back().se.se);
ans.pop_back();
while(comp(ans.back().se, cur)) ans.pop_back();
writc(ans.back().fi);
return 0;
}
T2. RGB Matching
显然,尽可能将同色狗配对,注意到最后至多有两组各剩下一只,假设一只红的一只蓝的,那么就有两种情况:
- 红的和蓝的配对;
- 从绿的中(前提是有绿的)拿出两只分别和红、蓝配对;
对于第一种,暴力枚举红色,找最近的蓝色,复杂度 \(\mathcal O(n\log n)\).
对于第二种,分别找到红色和蓝色中的最小值,加起来,复杂度 \(\mathcal O(n\log n)\).
但是如果这样处理第二种不会出现红色最小值与蓝色最小值找到同一只狗的情况吗?这种情况不否认,肯定是存在的,但是这种情况一旦出现,它肯定不会比第一种更优,具体地分析可以使用数轴法,发现即使绿点在红蓝中间,也不会比直接匹配红蓝更优,更不用说在其中一遍了,那显然是更劣。
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define siz(x) ((int)(x.size()))
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int maxn=1e5;
const ll inf=1ll<<60;
vector<ll>c[3];
int n;
inline void input(){
n=readin(1);
char s[5]; ll a;
rep(i, 1, n<<1){
a=readin(1ll); scanf("%s", s);
if(s[0]=='R') c[0].push_back(a);
else if(s[0]=='G') c[1].push_back(a);
else c[2].push_back(a);
}
}
ll ans=inf;
inline void special(){
ll ma=inf, mb=inf;
for(ll x: c[0]){
auto it=lower_bound(c[2].begin(), c[2].end(), x);
if(it!=c[2].end()) ma=min(ma, fab(x-(*it)));
if(it!=c[2].begin()){
--it;
ma=min(ma, fab(x-(*it)));
}
}
for(ll x: c[1]){
auto it=lower_bound(c[2].begin(), c[2].end(), x);
if(it!=c[2].end()) mb=min(mb, fab(x-(*it)));
if(it!=c[2].begin()){
--it;
mb=min(mb, fab(x-(*it)));
}
}
ans=min(ans, ma+mb);
}
signed main(){
input();
if(siz(c[0])%2==0 && siz(c[1])%2==0 && siz(c[2])%2==0)
return writc(0), 0;
if(siz(c[1])&1) swap(c[0], c[1]);
if(siz(c[2])&1) swap(c[1], c[2]);
sort(c[0].begin(), c[0].end());
sort(c[1].begin(), c[1].end());
sort(c[2].begin(), c[2].end());
// first situation
for(ll x: c[0]){
auto it=lower_bound(c[1].begin(), c[1].end(), x);
if(it!=c[1].end()) ans=min(ans, fab(x-(*it)));
if(it!=c[1].begin()){
--it;
ans=min(ans, fab(x-(*it)));
}
}
// second situation
if(siz(c[2])>=2) special();
writc(ans);
return 0;
}
T3. Odd Even Sort
构造题永远那么难 \(\sf qwq\).
考虑从大到小对每个数进行排序。如果这个数所处位置可以进行操作,那么我们可以一路操作到它想去的地方。但是如果无法操作,我们就需要考虑操作一个不会影响目标数字的位置且不会打乱已经排好的元素,使得操作奇偶性改变。
对于每个数字都可以这样操作,显然,在 \(k\ge 5\) 的情况都可以这样,但是对于 \(k=4\) 以及更小,我们发现,有些情况似乎并不能找到不会影响目标数字的位置,对于 \(k=4\),且操作为偶数,如果出现 1 3 4 2
,我们无法找到想要的位置 —— 但是我们可以手玩。其他 \(k=4\) 的情况,似乎也可以按照上面的进行处理。
对于 \(k=3\) 的情况,我们可以交替操作 \(1,2\) 号位置,不难发现无论如何都可以在有限步内排序数组,且操作次数不会超过 \(9\) 次 —— 这样是为了保证 \(n=3\) 的情况也是适用的。
代码不想打了。
关键点 —— 这种构造题,想一想较大情况的我们如何操作,对于十分小的情况但是不适配大情况的,我们可以就可以人工处理了......
T4. 1 or 2
一看到题目就肯定有一个贪心想法 —— 最大的和最小的匹配,次大的和次小的匹配。我们考虑证明它:
实际上可以抽象为这样一个问题:
\[\forall a\le b\le c\le d,\min\{a+c,b+d\}\le \min\{a+d,b+c\},\max\{a+c,b+d\}\ge \max\{a+d,b+c\} \]我想不需要严格说明了吧......
但是有可能有不够配对的情况?只选择一个数字 \(x\) 的情况,实际上就是 \(x+0\),即它与 \(0\) 配对。
考虑到 \(n\le 5000\),我们枚举添加 \(0\) 的数量,\(\mathcal O(n^2)\) 足以解决这个问题。
因为人太懒了,加入一个元素都要再调用一次 \(\tt sort()\),导致这份代码是 \(\mathcal O(n^2\log n)\) 的......
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int maxn=5000;
const ll inf=1ll<<60;
ll a[maxn*2+5]; int n;
inline void input(){
n=readin(1);
rep(i, 1, n) a[i]=readin(1);
}
inline int cmp(ll a, ll b){ return a>b; }
signed main(){
input();
int N=n; ll ans=inf;
rep(i, 0, N){ // enumerate the zero
ll mx=-inf, mn=inf;
if(i) a[++n]=0; // pay attention to the special situation
sort(a+1, a+n+1);
rep(j, 1, (n>>1)){
mx=max(mx, a[j]+a[n-j+1]);
mn=min(mn, a[j]+a[n-j+1]);
}
if(n&1) mx=max(mx, a[(n>>1)+1]), mn=min(mn, a[(n>>1)+1]);
ans=min(ans, mx-mn);
}
writc(ans);
return 0;
}
能够想到选择一个数实际上就是它和 \(0\) 配对是十分关键的。
T5. Directed Tree
注意到题目中的要求是 "对于每一个的 \(i\) 都不能从 \(a_i\) 到 \(i\)(经过一条或多条边)的序列",一个不合法,就全部崩盘。
如果我们正着求,那么我们要考虑到的东西比较多,并且,在子树中的数字填写情况显然有后效性,如果我们正着求,或许还得把子树中填的数字情况写进状态数组......
但是,我们能否求出不满足条件的数?考虑定义 \(f_{i,j}\) 表示子树 \(i\) 中,有 \(j\) 个点不合法的方案数。
由子树而来的转移是:
\[f_{i,j}=\sum_{v\in\text{son}_i}\sum_{k=0}^jf_{i,j-k}f_{v,k} \]在考虑当前点也是不合法点的时候:
\[f_{i,j+1}\leftarrow f_{i,j+1}+f_{i,j}\times (\text{siz}_u-j) \]考虑已经有 \(j\) 个点不合法了,我们再在剩下的 \(\text{siz}_u-j\) 个点中任意选择一个点对应的编号来填,那么当前点也是不合法的了。
答案是什么呢?由于我们的状态定义的是有 \(j\) 个点是不合法的,对于这 \(j\) 个点我们是确定的,但是剩下的 \(n-j\) 个点的填写情况我们还不知道,所以考虑使用容斥:
\[Ans=\sum_{i=0}^n(-1)^if_{1,j}(n-j)! \]时间复杂度 \(\mathcal O(n^2)\).
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int maxn=2000;
const int mod=998244353;
vector<int>g[maxn+5];
int f[maxn+5][maxn+5];
inline void add_edge(int u, int v){
g[u].push_back(v), g[v].push_back(u);
}
int n;
inline void input(){
n=readin(1); int par;
rep(i, 2, n) add_edge(readin(1), i);
}
int siz[maxn+5], tmp[maxn+5];
void dfs(int u, int par){
f[u][0]=1;
for(int v: g[u]) if(v!=par){
dfs(v, u);
rep(j, 0, siz[u]) rep(k, 0, siz[v])
tmp[j+k]=(tmp[j+k]+1ll*f[u][j]*f[v][k]%mod)%mod;
siz[u]+=siz[v];
rep(j, 0, siz[u]) f[u][j]=tmp[j], tmp[j]=0;
}
drep(j, siz[u]-1, 0) // consider this node is invalid
f[u][j+1]=(f[u][j+1]+1ll*(siz[u]-j)*f[u][j]%mod)%mod;
++siz[u]; // pay attention, you put it in the last
}
int fac[maxn+5];
signed main(){
input();
dfs(1, 0);
int ans=0;
fac[0]=1; rep(i, 1, n) fac[i]=1ll*fac[i-1]*i%mod;
rep(j, 0, n) ans=(0ll+ans+mod+(j&1? -1ll: 1ll)*f[1][j]*fac[n-j]%mod)%mod;
writc(ans, '\n');
return 0;
}
一个点如果想要合法,首先,不能和子树中的点重复,其次,它不能填子树中的点的编号 —— 这会有后效性,使得我们非常困难地处理 \(\tt DP\) 转移。但是如果我们考虑不合法,考虑十分简单 —— 只需要填入子树中的其中一个,并且不和子树中不合法的点填同样的数字即可。
T6. Logical Operations on Tree
感觉好像很复杂的样子。但是对于计数问题,无非就几种思路 —— 正难则反、转化容斥、挖掘性质。
为了简化题解,下面称一棵合法的树为妙妙树。
这道题显然是有性质的:考虑当一个叶子为 \(1\cup\) 时,那么我们只要最后操作这个叶子,无论如何最后都可以得到 \(1\) —— 即无论这个叶子连向什么样的树,这颗子树一定是妙妙树。
既然这个关于叶子的情况那么特殊,我们不妨考虑一下其他关于叶子的情况:
- 当叶子为 \(1\cap\) 时,显然,只有当它所连接的树能够得到 \(1\),加入它之后才能得到 \(1\),即要求它连接的树本来就是妙妙树;
- 当叶子为 \(0\cup\) 时,一样,只有当它所连接的树能够得到 \(1\),加入它之后才能得到 \(1\),即要求它连接的树本来就是妙妙树;
- 当叶子为 \(0\cap\) 时,它是个坏家伙,操作它之后新的点肯定会是 \(0\),但是如果能够在执行它之后的树都变成妙妙树,那么在没有它的时候原来的树更得是妙妙树了;
综上,只有当 \(1\cup\) 时比较特殊 —— 我们一定会得到一棵妙妙树,其他情况下都有 “原树为妙妙树” 的前提。
考虑如何统计?这里使用了容斥的办法,我们统计没有出现 \(1\cup\) 这种叶子的树的数量,再统计这些树中是妙妙树的情况,最后用 总方案-没有出现 \(1\cup\) 这种叶子的树的数量+没有出现 \(1\cup\) 这种叶子的树但是妙妙树的树的数量,至于没有算过的那些 —— 都是出现了 \(1\cup\) 叶子的树,它们一定会是妙妙树。
具体地,定义 \(\tt f[]\) 表示 “\(u\) 的子树中,没有出现 \(1\cup\) 这种叶子的树的数量”,定义 \(\tt g[]\) 表示 “\(u\) 的子树中,没有出现 \(1\cup\) 这种叶子的树但是妙妙树的树的数量”,那么对于 \(f\),我们有转移
\[f_u=\sum_{v\in\text{son}_u}2f_v-g_v \]乘 \(2\) 是因为连接的边有 \(\cap,\cup\) 两种可能,减去 \(g_v\) 是为了去除子树为 \(1\),且选边为 \(\cup\) 的情况 —— 这是我们指定要排除的,出现了 \(1\cup\) 的叶子。
对于 \(g_u\),我们也有
\[g_u=\prod_{v\in\text{son}_u}f_v \]可能这个转移有点奇怪,但是考虑我们的初始化:
\[f_u=2,g_u=1 \]表示 \(u\) 有选择 \(0,1\) 两种方式,并且它一个点是一个子树,对于 \(g_u=1\),显然,当 \(u\) 填 \(1\) 时,它自己构成的子树是妙妙树。
那么对于 \(g\) 的转移就很好理解了 —— 无论这颗子树是什么,我们都得要求原树是一棵妙妙树。将原树为妙妙树与子树的所有情况结合起来,就可以得到 \(g_u\) 了。为什么这里不 \(\times 2\) 呢?因为子树中的取值,不管是 \(0\) 还是 \(1\),都有固定的边配对,如果是 \(0\),那么边必须为 \(\cup\),如果子树为 \(1\),那么必须为 \(\cap\).
最后的答案就是
\[2^{2n-1}-f_1+g_1 \]时间复杂度 \(\mathcal O(n)\).
#include<cstdio>
#include<map>
#include<set>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int mod=998244353;
const int maxn=1e5;
inline int qkpow(int a, int n){
int ret=1;
for(; n>0; n>>=1, a=1ll*a*a%mod)
if(n&1) ret=1ll*ret*a%mod;
return ret;
}
vector<int>tre[maxn+5];
inline void add_edge(int u, int v){
tre[u].push_back(v), tre[v].push_back(u);
}
int n;
inline void input(){
n=readin(1);
int u, v;
rep(i, 2, n){
u=readin(1), v=readin(1);
add_edge(u, v);
}
}
int f[maxn+5], g[maxn+5];
void dfs(int u, int par){
f[u]=2, g[u]=1;
for(int v: tre[u]) if(v!=par){
dfs(v, u);
f[u]=1ll*f[u]*((f[v]<<1ll)%mod+mod-g[v])%mod;
g[u]=1ll*g[u]*f[v]%mod;
}
}
signed main(){
input();
dfs(1, 0);
int ans=qkpow(2, (n<<1)-1);
writc((0ll+ans+mod-f[1]+g[1])%mod);
return 0;
}