前言
这是一篇攒了很久的题解()
一、A. Antimedian Deletion
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>p(n+1);
for(int i=1;i<=n;i++)
{
cin>>p[i];
}
if(n==1)
{
cout<<1<<endl;
return ;
}
for(int i=1;i<=n;i++)
{
cout<<2<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先,若只有一个数,那么肯定就是这个数自己。否则,对于每个位置上的数,只要能选,肯定是存在最大值和最小值的。那么每次肯定可以删除除了自己以外的数,所以最终答案就都是 2,即删到选不了为止。
二、B. Mickey Mouse Constructive
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
const int MAXN=2e5;
vector<int>cnts(MAXN+1);
void solve()
{
int x,y;
cin>>x>>y;
int k=1;
if(x<y)
{
swap(x,y);
k=-1;
}
cout<<cnts[x-y]<<endl;
for(int i=1;i<=y;i++)
{
cout<<-k<<" ";
}
for(int i=1;i<=x;i++)
{
cout<<k<<" ";
}
cout<<endl;
}
void init()
{
cnts[0]++;
for(int i=1;i<=MAXN;i++)
{
for(int j=i;j<=MAXN;j+=i)
{
cnts[j]++;
}
}
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为 1 和 -1 构成相反数,是对称的,所以先认为个数多的是 1,个数少的是 -1。
若全是 1,此时可以发现子数组的累加和必然是数组长度 n 的因数。考虑将每个 1 看作一个点,那么每添加一个 -1,都相当于将两个 1 合并成一个节点,使得节点累加和保持为 1。那么对于 x 个 1 和 y 个 -1 的数组,答案就是 x-y 的因数个数。
那么在构造时,直接把所有 -1 全堆到开头即可。注意如果交错放的话会导致开头的 1 有自己和参与后续两种划分方法,所以不合法。
三、C1. Equal Multisets (Easy Version)
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,k;
cin>>n>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
vector<int>cnts(n+1);
for(int i=1;i<=n;i++)
{
if(b[i]!=-1)
{
cnts[b[i]]++;
}
}
for(int i=1;i<=n;i++)
{
if(cnts[i]>1)
{
NO;
}
}
vector<int>pa(n+1);
vector<int>pb(n+1);
for(int i=1;i<=n;i++)
{
pa[a[i]]=i;
if(b[i]!=-1)
{
pb[b[i]]=i;
}
}
for(int i=1;i<=n;i++)
{
if(pb[i]==0)
{
continue;
}
int x=min(pa[i],pb[i]);
int y=max(pa[i],pb[i]);
if(x==y)
{
continue;
}
if(y>k||x<n-k+1)
{
NO;
}
}
YES;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于每个窗口,考虑当前即将入窗口和出窗口的数,分别表示为 ai,ao,bi,bo。此时可以发现,若 ao 当前和 b 中某元素 x 匹配,若 x 恰好是 bo,那么就可以正常出窗口。否则,即在 ao 出窗口后,x 仍会留在窗口内。由于窗口长度相等,那么就必须保证 x 和 a 中的某个数匹配。又因为此时 a 是个排列,所以必然不可能存在两个数都等于 x。所以在 ao 出窗口时,其匹配的数必然跟着出窗口,即 bo。所以也就需要保证,bi 和 ai 恰好是匹配的。
观察样例可以发现,会存在一些数永远在窗口内。此时这些数其实只需要保证在 b 中存在一个数和其对应即可,顺序其实是无所谓的。
所以,既然要求必须两两配对,又因为 a 是个排列,那么就必须保证 b 也是个排列。所以在实现时先检查每个数是否最多出现一次,再记录每个数在两数组中出现的位置,判断是否永远在同一个窗口内即可。
四、C2. Equal Multisets (Hard Version)
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
//自定义
DSU(int n){
father.assign(n,0);
for(int i=0;i<n;i++){
father[i]=i;
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx==fy){
return false;
}
father[fx]=fy;
return true;
}
};
void solve()
{
int n,k;
cin>>n>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
DSU dsu(n+1);
for(int l=1,r=k+1;r<=n;l++,r++)
{
if(a[l]!=a[r])
{
if(b[l]!=-1&&b[l]!=a[l])
{
NO;
}
if(b[r]!=-1&&b[r]!=a[r])
{
NO;
}
b[l]=a[l];
b[r]=a[r];
}
else
{
dsu.merge(l,r);
}
}
for(int i=1;i<=n;i++)
{
if(b[i]==-1)
{
continue;
}
int fa=dsu.find(i);
if(b[fa]!=-1&&b[fa]!=b[i])
{
NO;
}
b[fa]=b[i];
}
for(int i=1;i<=n;i++)
{
if(b[i]!=-1)
{
continue;
}
int fa=dsu.find(i);
b[i]=b[fa];
}
vector<int>cnts(n+1);
for(int i=1;i<=k;i++)
{
cnts[a[i]]++;
if(b[i]!=-1)
{
cnts[b[i]]--;
}
}
for(int i=1;i<=n;i++)
{
if(cnts[i]<0)
{
NO;
}
}
for(int l=1,r=k+1;r<=n;l++,r++)
{
cnts[a[l]]--;
if(b[l]!=-1)
{
cnts[b[l]]++;
}
cnts[a[r]]++;
if(b[r]!=-1)
{
cnts[b[r]]--;
}
if(cnts[a[l]]<0)
{
NO;
}
if(b[r]!=-1&&cnts[b[r]]<0)
{
NO;
}
}
YES;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于任意的情况,此时交叉匹配就可以合法了。也就是说,若 ao 和 b 中某元素 x 匹配,那么也可以存在 x 和 ai 匹配的情况。此时,就需要 bi 恰好能补上 bo 缺失的位置,即要求 bi=bo。此时也就需要让 ai=ao,即 b 中存在一个元素 x 能和这两个数匹配。
那么若 ai\neq ao,由于此时需要 ai=bi,ao=bo,所以若不满足直接返回即可,然后就可以直接给 bi 和 bo 赋值了。否则的话,此时就要求 bi=bo,这个可以通过并查集维护。
在通过并查集维护好相等关系后,检查所有已经填过的数,若其和代表节点的值不同就说明不合法,否则就可以填代表节点了。之后再考察所有没填过的位置,将其填成其代表节点的值。
最后再滑窗模拟一遍判断即可。过程中维护每个数出现的词频,a 数组认为增加,b 数组认为抵消。每次 a 数组出窗口和 b 数组入窗口时检查词频是否为负数,即无法匹配即可。
五、D. AND-array
这个 D 我真感觉很难啊为什么这么多人过......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+7;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n;
cin>>n;
vector<Z>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
const int MAXP=28;
vector<int>c(MAXP+1);
for(int i=n;i>=1;i--)
{
for(int p=MAXP;p>=0;p--)
{
b[i]-=C(c[p],i)*power((Z)2,p);
}
ll rest=b[i].val();
for(int p=MAXP;p>=0;p--)
{
if(rest>>p&1)
{
c[p]=i;
}
}
}
vector<ll>ans(n+1);
for(int p=MAXP;p>=0;p--)
{
for(int i=1;i<=c[p];i++)
{
ans[i]|=1ll<<p;
}
}
for(int i=1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这题从各种方面上来看都特别怪......
对于这种累加和问题,还是考虑贡献法。那么对于第 i 位,若当前子序列长度为 k,其能产生贡献当且仅当这 k 个元素的第 i 位全是 1。那么若整个数组第 i 位上是 1 的个数为 ,那么第 i 位产生贡献的次数就是
,所以总贡献就是
。
考虑求出 ,可以发现直接求很难求,因为
是模意义下的数。此时对脑电波 guess 一下,可以发现对于组合数,当
时,当前位是不会产生贡献的。而当
时,此时产生的贡献就是当前位的位权。又因为正着求很难求
,所以考虑时光倒流,倒着构造
。
此时,由于所有大于 k 的 在之前已经确定,所以可以先把这些贡献减去,剩下的就是所有
产生的贡献。又因为此时贡献的次数是 1,所以按位分解后是 1 的位就表示
。
减去之前的贡献好说,直接在模意义下操作即可。但由于后续需要按位分解,那就必须保证其就是原本的数。再对脑电波可以发现,即使每一位全是满的, 也是小于 1e9+7 的,所以经过前面减完的结果必然就是原本的数。
六、E. Minimum Path Cover
感觉 E 还好啊为什么这么少人过()
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
T gcd(T a,T b){
return b==0?a:gcd(b,a%b);
}
template<typename T>
T lcm(T a,T b){
return a/gcd(a,b)*b;
}
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
vector<vector<int>>g(n+1);
vector<ll>dp(n+1);
vector<ll>sum(n+1);
for(int u=n;u>=1;u--)
{
int k;
cin>>a[u]>>k;
for(int i=1,v;i<=k;i++)
{
cin>>v;
g[u].push_back(v);
}
sum[u]=1;
int ok=0;
for(auto v:g[u])
{
dp[u]+=dp[v];
ll g=gcd(sum[v],a[u]);
if(g>1)
{
ok=1;
}
sum[u]=lcm(sum[u],g);
}
if(!ok)
{
dp[u]++;
sum[u]=a[u];
}
cout<<dp[u]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个交互莫名其妙的,强制在线是何意味呢??
根据这个给出的顺序,不难想到要进行 dp,即当前节点 u 的答案从所有孩子节点得到。
还是从叶子节点开始考虑,那么叶子节点肯定是只能自己单开一条路径。之后在往上的时候,当前节点 u 就可以从其所有孩子,即叶子节点继承过来。那么若其和所有孩子都构不成一条 gcd 大于 1 的路径,那么就必须新开一条路径。
重点是能和多个孩子构成路径的情况。首先,如果在当前节点 u 新开路径,即使孩子能跟 u 构成路径,容易证明这样操作必然是不赚的,因为会使得后续的可能性更小。所以为了在向上的过程中保留所有可能的 gcd,考虑对所有能组成一条路径的孩子的 gcd 取并集,即 lcm。所有多开一个数组存每个孩子能往上传的 lcm 即可。注意在取 lcm 的时候先取 gcd,再对 gcd 取 lcm,这样就能保证参与构成 lcm 的数必然是当前点权的因数。
感觉现在题都好脑电波,都不好写思维链的......
总结
F 太超模了,原本看题解有范德蒙德卷积就以为能补,结果大战了两个小时后仍无法理解变体的范德蒙德卷积,遂放弃。