Task.1 小L的序列
题目大意:
给出\(f_1,f_2,...f_k\)和\(b_1,b_2,...b_k\)。
\(f_i=(f_{i-1}^{b_1}\cdot f_{i-2}^{b_2} \cdot\cdot\cdot f_{i-k}^{b_k})mod \ p\ (i\geq k)\)
求\(f_n\)。
数据范围:\(1\leq N\leq 4\cdot10^6,1\leq K\leq 200,P=998244353,1\leq f_i,b_i\leq P\)
PS:以前在CF里见到过一个\(k=3\)的情况,当时再考场上推到第8项发现每一项的指数都可以递推,当时搞不明白为什么,今天想的时候就清楚了。
最终的答案一定是每个\(f_i\)的若干次方的乘积组成的,考虑计算每个\(f_i\)最终的指数。我们前\(k\)项中的某一项在第\(i\)个位置的指数是\(dp_i\),两个\(f_i\)相乘是加法操作,\(f_x\)的次方是乘法操作,那么就有\(dp_i=\Sigma_{j=1}^{j\leq k} dp_{i-j}\cdot b_j\),根据费马小定理,我们需要对\(dp\)模\(P-1\)。
这个转移可以用矩阵乘法优化:
对于前\(k\)项中的每个位置,只要分开做就可以得到每个位置最后的指数,当然,把每个位置的初始矩阵拼起来做一次也是可以的。
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
template<class T>void read(T &x){
x=0; char c=getchar();
while(c<'0'||'9'<c)c=getchar();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef long long ll;
const int K=205;
const int M=998244353;
int n,m,a,b[K];
ll ans=1ll;
struct matrix{
int x[K][K],r,c;
void clear(){memset(x,0,sizeof(x));}
matrix operator * (const matrix &_)const{
matrix __; __.clear();
__.r=r; __.c=_.c;
for(int k=1;k<=c;k++)
for(int i=1;i<=r;i++) if(x[i][k])
for(int j=1;j<=_.c;j++) if(_.x[k][j])
__.x[i][j]=(__.x[i][j]+1ll*x[i][k]*_.x[k][j]%(M-1))%(M-1);
return __;
}
}u,v,w;
int qpow(int x,int y){
ll tmp=1,base=x;
while(y){
if(y&1)tmp=(tmp*base)%M;
base=(base*base)%M; y>>=1;
} return tmp;
}
matrix mpow(matrix _,int _b){
matrix res,base=_;
res.clear(); res.c=res.r=m;
for(int i=1;i<=m;i++)res.x[i][i]=1;
while(_b){
if(_b&1)res=res*_;
_=_*_; _b>>=1;
} return res;
}
int main(){
// freopen("seq.in","r",stdin);
// freopen("seq.out","w",stdout);
read(n); read(m);
if(n<=m){
while(m--)read(a);
while(n--)read(a);
printf("%d\n",a);
return 0;
}
for(int i=1;i<=m;i++) read(b[i]);
for(int i=1;i<=m;i++)v.x[i][m]=b[m-i+1], v.x[i][i-1]=1;
v.c=v.r=m; v=mpow(v,n-m);
for(int i=1;i<=m;i++){
read(a);
u.clear(); u.x[1][i]=1;
u.c=m; u.r=1; u=u*v;
ans=(ans*qpow(a,u.x[1][m]))%M;
}
printf("%lld\n",ans);
return 0;
}
Task.2 梦境
题目大意:给\(n\)出个区间\([l_i,r_i]\),\(m\)个时间点\(t_i\)。如果区间包含时间点就可以把区间和时间点融合,每个区间和时间点只能被选一次。求最多能融合多少对区间和点。
数据范围:\(1\leq N,M\leq 2\cdot 10^5,1\leq l_i,r_i,t_i\leq 1\cdot 10^9\)
问题明显是求二分图最大匹配。但是点数边数规模太大,只能拿部分分,根据本题的特殊性,考虑贪心。把所有区间按左端点从小到达排序,从小到大枚举时间点,每次把左端点小于当前时间点的区间放进一个以右端点为关键字的小根堆中。如果堆顶区间右端点小于时间点就把它扔掉,否则它一定包含当前时间点,统计进答案,弹掉。这样做一定会优先把长度短的影响小的区间处理掉。
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
template<class T>void read(T &x){
x=0; char c=getchar();
while(c<'0'||'9'<c)c=getchar();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
const int N=200050;
int n,m,t[N],ans;
struct seg{
int l,r;
bool operator >(const seg _)const{return r<_.r;}
bool operator <(const seg _)const{return r>_.r;}
}s[N];
priority_queue<seg>q;
bool cmp(seg _,seg __){return _.l<__.l;}
int main(){
freopen("dream.in","r",stdin);
freopen("dream.out","w",stdout);
read(n); read(m);
for(int i=1;i<=n;i++){ read(s[i].l); read(s[i].r);}
for(int i=1;i<=m;i++) read(t[i]);
sort(s+1,s+n+1,cmp); sort(t+1,t+m+1); int p=0;
for(int i=1;i<=m;i++){
if(p<n) while(p<n&&s[p+1].l<=t[i]) q.push(s[++p]);
if(!q.empty()) while(!q.empty()&&q.top().r<t[i]) q.pop();
if(!q.empty()) if(q.top().l<=t[i]&&t[i]<=q.top().r) ++ans, q.pop();
}
printf("%d\n",ans);
return 0;
}
Task.3 树
题目大意:给出一棵\(n\)个点的树,和\(m\)个点对\((x_i,y_i),(x_i\ne y_i)\),要求统计树上所有路径中不同时包含任何一对\((x_i,y_i)\)的路径条数(\((u,v),(v,u)\)算一对)。
数据范围:\(1\leq N,M\leq 1\cdot 10^5\),对于部分数据树的形态是一条链。
既然题目要我们求不同时包含一对\((x_i,y_i)\)的路径,我们不妨考虑去求同时包含一对\((x_i,y_i)\)的路径数。
对于树的形态是一条链的情况,我们把问题转化成在序列上求有多少个区间同时包含位置\((dfn_{x_i},dfn_{y_i}),(dfn_{x_i}\leq dfn_{y_i})\)。对于每一对\((x_i,y_i)\),答案就是\([1,dfn_{x-I}]\)这段区间和\([dfn_{y_i},n]\)这段区间中的点对产生的贡献。考虑一下把这种情况搬到树上:(在这里默认树上\(x\)的深度小于\(y\)的深度)
\(x\)和\(y\)间不存在父子关系:
如图,\(x\)和\(y\)的\(dfn\)序一定是两段不相交的区间,问题转化为求满足\(u\in[dfn_x,dfn_x+siz_x]\)和\(v\in[dfn_y,dfn_y+siz_y]\)的\(u\)和\(v\)有多少对。
\(x\)和\(y\)间存在父子关系:
同时经过\(x,y\)的路径端点一定是在\(y\)的子树内的点(\(dfn\)连续)和从树上去掉\(x\)的子树的其他节点组成。
同样的,这种情况也可以放在区间上做:明显有\(u\in[dfn_y,dfn_y+siz_y]\),其他的点?其他的点处在连续的区间内吗?是的,在进到某个节点\(z\in son_x\ and\ z\in ancestor_y\)之前的点和\(u\)构成的路径一定经过\(x,y\),在离开子树\(z\)后进到的点和\(u\)组成的路径也一定经过\(x,y\),这在\(dfn\)序中也是连续的区间。
问题转化为求满足\(u\in[dfn_y,dfn_y+siz_y]\)和\(v\in[1,dfn_z-1]\cap[dfn_z+1,n]\)的\(u\)和\(v\)有多少对。
现在建立一个关于\(dfn\)序的平面直角坐标系,把问题中作为条件的区间搬上去,单个问题的答案就变成求解一个矩形内的点的个数(面积),同时满足所有条件的问题放在一起就是求这些矩形面积的交。至此问题用扫描线就可以解决了。
在细节上,对于单个问题的两个区间\([l1,r1],[l2,r2]\),矩形的两个顶点(左下、右上)应该是\((l1,l2)\ (r1,r2)\)还是\((l2,l1)\ (r2,l2)\)呢?还是说要计算两个矩形的面积呢?其实只需要计算一个:如果添加两个矩形,他们一定关于直线\(y=x\)对称,最后算出来的结果需要除\(2\);但是每次的两个区间都是不相交的两个区间,所形成的矩形不会经过直线\(y=x\),所以我们可以只计算这条直线一边(这里默认计算直线下面)的矩形,即左上方顶点\((l1,r2)\)在直线\(y=x\)下方,即\(l1>r2\)。这样我们就减小的计算量。
代码:(实现的有点繁琐,而且还被卡栈空间了,换根才过去)
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
template<class T>void read(T &x){
x=0; char c=getchar();
while(c<'0'||'9'<c)c=getchar();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef long long ll;
typedef pair<int,int> pr;
const int N=100050;
int n,m;
vector<int>e[N];
int dep[N],siz[N],fa[N],son[N];
int top[N],dfn[N],tail[N],cnt;
ll ans;
vector<pr> in[N],del[N];
struct segt{
int s[N<<2],rm[N<<2];
void pushup(int l,int r,int p){
if(rm[p]) s[p]=r-l+1;
else if(l==r) s[p]=0;
else s[p]=s[p<<1]+s[p<<1|1];
}
void add(int L,int R,int c,int l,int r,int p){
if(L<=l&&r<=R){ rm[p]+=c; pushup(l,r,p); return ;}
int mid=(l+r)>>1;
if(L<=mid) add(L,R,c,l,mid,p<<1);
if(R>mid) add(L,R,c,mid+1,r,p<<1|1);
pushup(l,r,p);
}
}t;
void dfs1(int x){
siz[x]=1; son[x]=0;
for(int i=e[x].size()-1,y;~i;i--){
y=e[x][i]; if(y==fa[x]) continue;
dep[y]=dep[x]+1; fa[y]=x;
dfs1(y); siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
void dfs2(int x,int tp){
top[x]=tp; dfn[x]=++cnt;
if(son[x])dfs2(son[x],tp);
for(int i=e[x].size()-1,y;~i;i--){
y=e[x][i]; if(y==son[x]||y==fa[x]) continue;
dfs2(y,y);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) y=fa[top[y]];
else x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int find(int x,int y){
while(top[x]!=top[y]){y=top[y]; if(fa[y]==x) return y; y=fa[y];}
return son[x];
}
void insert(int x1,int x2,int y1,int y2){
in[y1].push_back(pr(x1,x2));
del[y2].push_back(pr(x1,x2));
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
read(n); read(m); ans=1ll*n*(n-1)/2; int x,y;
for(int i=1;i<n;i++){
read(x); read(y);
e[x].push_back(y);
e[y].push_back(x);
}
fa[6]=6; dep[6]=1; dfs1(6); dfs2(6,6); cnt=0;
for(int i=1;i<=n;i++) tail[i]=dfn[i]+siz[i]-1;
for(int i=1,sx;i<=m;i++){
read(x); read(y);
if(dfn[x]>dfn[y]) swap(x,y);
if(lca(x,y)==x){
sx=find(x,y);
if(dfn[y]>1) insert(dfn[y],tail[y],1,dfn[sx]-1);
if(tail[y]<n) insert(tail[sx]+1,n,dfn[y],tail[y]);
}
else insert(dfn[y],tail[y],dfn[x],tail[x]);
}
pr a;
for(int i=1;i<=n;i++){
for(int j=in[i].size()-1;~j;j--){a=in[i][j]; t.add(a.first,a.second,1,1,n,1); }
ans-=t.s[1];
for(int j=del[i].size()-1;~j;j--){a=del[i][j]; t.add(a.first,a.second,-1,1,n,1); }
}
printf("%lld\n",ans);
return 0;
}