0
T1 rz题。
1
题目背景
弗洛吉有一棵 \(n\) 个顶点的无根树,每个顶点 \(i\) 的初始权重为 \(i\)。他最多执行一次操作,一次操作是选择一条路径上的顶点序列 \(x_1,x_2,\cdots,x_k\),从集合 \(x_1,x_2,\cdots,x_k\) 中,选择偶数个顶点(顶点可以重复选择)\(u_1,u_2,\cdots,u_{2m}\)。对于所有的 \(i\in[1,m]\),依次交换顶点 \(u_{2i-1},u_{2i}\) 的权重。问题描述
求通过一次操作后,能得到的不同的权重序列 \(a_1, a_2, \cdots, a_n\) 的数量(对 \(998244353\) 取模)。数据范围
- 测试用例数 \(T \leq 10^5\)
- 每个测试用例的顶点数 \(n \leq 3000\)
- 所有测试用例的 \(n\) 总和 \(\leq 1.5 \times 10^4\)
操作只有一次是很爽的。
因为不管选什么链,链上的点组成的序列中每个数两两不同,于是我们关心的只是选出的链的长度问题,长度相同的链,经过交换所得到的种类数是相同的。
下一步就是去重问题,考虑 DP,从长度 \(i-1\) 到长度 \(i\),怎么转移过来情况数。
首先,每次考虑一个链的时候都会考虑上不变的情况,但统计是全局的,只会统计一次,于是先把这个东西扔出去。
接下来,考虑把新的数 \(i\) 插空,也就是有 \(i\) 个空可以插,但是 \(i-1\) 不变的情况就只能插到 \(i-1\) 个空里去了,于是我们有:
\(f_i=f_{i-1}\times i + i-1\)
但是这样是有重复的,也就是改 \(f_i\) 中长度为 \(j\) 的一串的情况数会被算到 \(f_j\) 里面去。
算重了什么?算重的是那些不是改变整个序列的情况,具体来说,是移动了里面那些 \(i-1,i-2\cdots\) 那些子段的情况,我们会在 DFS 的时候把他们算上的,现在不需要他们了。
于是我们这么处理,令 \(g_i=f_i-2\times f_{i-1}+f_{i-2}\)
其实之前想过各种去重的式子,基本上就是这么个格式,这个东西是快破防了随便改改出来的(
理解一下,就是 \(f_{i-1}\) 包含了所有移动了里面那些 \(i-1,i-2\cdots\) 那些子段的情况,于是把两头减去,中间多减了一段 \(i-2,i-3\cdots\) 里面的情况,加回来就是 \(+f_{i-2}\)。
于是算出这个东西之后 DFS 即可。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){ll x=0,f=1;char c=getchar();while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}return x*f;
}
const int N=4000;
const int mod=998244353;
vector<int> e[N];
bitset<N> vis;
int ans=1;
int n;
ll pa[N];
ll psum[N];
ll ca[N];
map<pair<int,int>,bool> mph;
void dfs(int s1,int u,int len){vis[u]=1;
// pair<int,int> p;
// p.first=min(s1,u);p.second=max(s1,u);if(s1<u){ans=(ans+ca[len])%mod;}for(int v:e[u]){if(!vis[v])vis[v]=1,dfs(s1,v,len+1);}return ;
}
void init(){ans=1;mph.clear();for(int i=0;i<=n;i++)e[i].clear();return ;
}
void solve(){cin>>n;for(int i=1;i<n;i++){int a,b;cin>>a>>b;e[a].push_back(b);e[b].push_back(a);}for(int i=1;i<=n;i++){vis.reset();dfs(i,i,1);// cout<<i<<'\n';}cout<<ans<<'\n';return ;
}
int main(){freopen("shuffling.in","r",stdin);freopen("shuffling.out","w",stdout);pa[1]=0;int len=1;for(int i=2;i<=3500;i++){pa[i]=(pa[i-1]*(len+1)%mod+len)%mod;len++;ca[i]=(pa[i]+pa[i-2]-2*pa[i-1]+mod)%mod;}int t;cin>>t;while(t--){init();solve();}return 0;
}
2 NOI2005 聪聪与可可
比较易懂的概率 DP,我咋又放了去调 T4 了。
因为猫总是能比鼠走的步数多一步,于是猫总是可以捉到鼠,如果捉不到,也会在一次行动后将与鼠的最短距离至少缩小 \(1\)。
概率 DP 经典套路就是从结果往前面推,于是 \(f_{i,j}\) 表示猫在 \(i\) 鼠在 \(j\) 时候的时间期望。
考虑枚举 \(i\) 的所有二阶邻接点和鼠的邻接点 \(u,v\) 进行转移,\(f_{u,v}\) 可以转移到 \(f_{i,j}\) 的条件是 \(u,v\) 间的最短路径长小于 \(i,j\) 之间的。
到达 \(u,v\) 这个状态的条件也是可以算出的,于是这题就做完了。
还没有代码。
3
题目背景
将一排商店划分为三个连续部分,求使"普通"颜色数量最大的划分方案。问题描述
给定 \(n\) 个商店的颜色数组,选择两个分割点 \(b_1\) 和 \(b_2\)(满足 \(1 < b_1 < b_2 \leq n\))将数组分为三部分:\([1, b_1-1]\)、\([b_1, b_2-1]\) 和 \([b_2, n]\)。一个颜色 \(x\) 是"普通的"当且仅当它在三个部分中都至少出现一次。求最大的普通颜色数量。数据范围
- 测试数据组数 \(T \leq 10\)
- 数组长度 \(n \leq 150,000\)
- 颜色 \(a_i \leq 10^6\)
比较经典的线段树。
考虑我们先确定左边的那个分割点,然后快速找到右边的所有位置中,放下分割点收益最大的。
这就要求我们根据分割点统计右边所有位置的贡献。
单独统计出所有颜色种类的开始位置和结束位置,从前往后枚举左分割点。如果一种颜色在左分割点左侧出现过,那么我们把右分割点放在之后第一次出现该颜色的位置到结束位置前一个位置才能使这个颜色被计入答案中。
于是将这一段区间右分割点的贡献加 \(1\)。
随着左端点的移动,有一部分区间的贡献是要更新的,也就是改成在此之后第一次出现改颜色的位置,这个区间减 \(1\) 也是很好维护的。
于是我们在每一次右移左分割点时候区间查最大值就可以了。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){ll x=0,f=1;char c=getchar();while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}return x*f;
}
const int N=1e5+5;
const int M=2e6+6;
int b1[N*10],e1[N*10];
int arr[N];
int reg[M];
bitset<M> inp;
struct seg{int l,r;int cnt;int ma;bool cl;
}t[N];
void b(int p,int l,int r){t[p].l=l;t[p].r=r;t[p].ma=t[p].cnt=0;if(l==r){return ;}int mid=l+r>>1;b(ls,l,mid);b(rs,mid+1,r);return ;
}
void pushdown(int p){if(t[p].cl){t[ls].cnt=t[ls].ma=0;t[ls].cl=1;t[rs].cnt=t[rs].ma=0;t[rs].cl=1;t[p].cl=0;}return ;
}
void asi(int p,int l,int r,int k){if(t[p].l==l&&t[p].r==r){t[p].ma=k;if(t[p].ma!=0)t[p].cnt=1;else t[p].cnt=0;return ;}pushdown(p);int mid=t[p].l+t[p].r>>1;if(l<=mid)asi(ls,l,r,k);if(mid<r) asi(rs,l,r,k);t[p].ma=max(t[ls].ma,t[rs].ma);t[p].cnt=t[ls].cnt+t[rs].cnt;return ;
}
seg gmx(){return t[1];}
vector<int> vs;
void init(){memset(b1,0,sizeof b1);memset(e1,0,sizeof e1);memset(arr,0,sizeof arr);memset(reg,0,sizeof reg);inp.reset();vs.clear();return ;
}
void solve(){int n;cin>>n;for(int i=1;i<=n;i++){arr[i]=rd;vs.push_back(arr[i]);}sort(vs.begin(),vs.end());int bpm=unique(vs.begin(),vs.end())-vs.begin();for(int i=0;i<bpm;i++){reg[vs[i]]=i+1;}for(int i=1;i<=n;i++){if(b1[reg[arr[i]]]==0)b1[reg[arr[i]]]=i;}for(int i=n;i>=1;i--){if(e1[reg[arr[i]]]==0)e1[reg[arr[i]]]=i;}b(1,1,bpm+1);int ans=0;int lft=0,rgt=0;for(int i=1;i<=n;i++){if(i==b1[reg[arr[i]]]&&i!=e1[reg[arr[i]]]){inp[reg[arr[i]]]=1;t[1].cl=1;t[1].ma=t[1].cnt=0;}else if(i==e1[reg[arr[i]]]&&i!=b1[reg[arr[i]]]){inp[reg[arr[i]]]=0;asi(1,reg[arr[i]],reg[arr[i]],0);}else if(inp[reg[arr[i]]]==1)asi(1,reg[arr[i]],reg[arr[i]],b1[reg[arr[i]]]);int b11=gmx().ma+1;int b22=i+1;int res=gmx().cnt;if(res>ans){lft=b11;rgt=b22;ans=res;}}cout<<ans<<'\n';cout<<lft<<' '<<rgt<<'\n';
}
int main(){freopen("yuuka.in","r",stdin);freopen("yuuka.out","w",stdout);int T;cin>>T;while(T--){init();solve();}return 0;
}