Part.I 游记
又是爆炸的一天。。。
今天上午10点45左右收到了来自WLS的QQ。。。(然而我并没有收到QAQ…我下午才发现。。。
今天第一题的转移就像一个矩乘的题,然而我连矩阵都没有构造出来。。。然后就只有30的暴力分了。。。
第二题看上去就是个二分图匹配,然后我就愉快的打了个匈牙利。。。然后居然RE了。。。(我写得太丑了。。。(考完发现它就是贪心。。。
第三题最后才发现至少能骗90分。。。然而我没有时间去打了。。
Part.II 题解
A.小L的数列
题目
题目描述
输入
一行两个整数n和k。
之后1行k个正整数b1…bk。
之后1行k个正整数f1…fk。
输出
输出一个整数表示fn
样例输入
【样例输入1】
5 4
1 2 3 4
4 3 2 1
【样例输入2】
100000 4
1 2 3 4
12 23 34 45
样例输出
【样例输出1】
27648
【样例输出2】
33508797
数据范围
对于30%的数据,n≤10000.
对于另外20%的数据,bi=1,n≤1000000.
对于另外20%的数据,f[1]…f[k-1]=1.
对于另外20%的数据,k≤30.
对于100%的数据,1≤k≤200,1≤n≤40000000,1≤bi,fi≤998244352.
提示
样例解释:122333444*4=27648
分析
不难发现每步转移的实质是相同的,所以我们考虑利用矩阵乘法解决这道题,因为幂的乘法就相当于指数的加法,幂的乘方就相当于指数的乘法。
对于任意一个数 f i ( i ≥ k ) f_i(i\ge k) fi(i≥k),我们都可以将它表示成 f 1 a 1 f 2 a 2 ⋯ f k a k f_1^{a_1}f_2^{a_2}\cdots f_k^{a_k} f1a1f2a2⋯fkak,所以我们只求出它的每个数的指数就可以了。
对于一个数 f j f_j fj,当它处在第 i i i个位置时,它的指数就会乘上 b i b_i bi。所以我们可以构造一个对角线为 1 1 1,第一列为 b 1 , b 2 , … , b k b_1,b_2,\ldots,b_k b1,b2,…,bk的矩阵,对于每个数做快速幂即可。
这样时间复杂度显然是过不去的。不难发现对于每个数,所构造出的矩阵都是一样的,所以我们可以先预处理出这个矩阵,最后统计答案即可。
参考代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxk=200;
const int Mod=998244353;
int N,K,B[Maxk+5],f[Maxk+5];
struct Matrix {
int a[Maxk+5][Maxk+5];
};
Matrix operator * (Matrix &lhs,Matrix &rhs) {
Matrix ret;
memset(ret.a,0,sizeof ret.a);
for(int i=1;i<=K;i++)
for(int j=1;j<=K;j++)
for(int k=1;k<=K;k++)
ret.a[i][k]=(ret.a[i][k]+1LL*lhs.a[i][j]*rhs.a[j][k]%(Mod-1))%(Mod-1);
return ret;
}
inline int QuickPow(int a,int k) {
int ret=1;
while(k) {
if(k&1)ret=(1LL*ret*a)%Mod;
a=1LL*a*a%Mod;
k>>=1;
}
return ret;
}
inline Matrix QuickPow(Matrix t,int k) {
Matrix ret;
memset(ret.a,0,sizeof ret);
for(int i=1;i<=K;i++)
ret.a[i][i]=1;
while(k) {
if(k&1)ret=ret*t;
t=t*t;
k>>=1;
}
return ret;
}
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
scanf("%d %d",&N,&K);
for(int i=1;i<=K;i++)
scanf("%d",&B[i]);
for(int i=1;i<=K;i++)
scanf("%d",&f[i]);
if(N<=K) {
printf("%d\n",f[N]);
return 0;
}
Matrix res;
res.a[1][1]=B[1];
for(int i=2;i<=K;i++)
res.a[i][i-1]=1,res.a[1][i]=B[i];
res=QuickPow(res,N-K);
int ans=1;
for(int i=1;i<=K;i++)
ans=1LL*ans*QuickPow(f[K-i+1],res.a[1][i])%Mod;
printf("%d\n",ans);
return 0;
}
B.梦境
题目
题目描述
输入
输出
样例输入
2 2
1 3
2 4
1
3
样例输出
2
数据范围
分析
这道题不难看出是个二分图最大匹配问题,可以用二分图+网络流骗到70分(网络流的复杂度其实是跑不满的。
那么对于100分的数据,不难发现难点其实是在建图上。不难发现将转折点按时间顺序排好后,对于一个梦境,它其实是对应了一段连续的转折点,所以我们可以采用线段树优化,最后再跑一跑ISAP就可以了。(这样似乎就可以拿到100分了。。。
考虑贪心,对于一个转折点,我们选取左端点离它最近的点与它配对,用优先队列维护即可(正确性我不会证QAQ。。。
参考代码
贪心
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const int Maxn=200000;
int N,M;
struct Node {
int l,r;
bool operator < (const Node &rhs) const {return l==rhs.l?r<rhs.r:l<rhs.l;}
};
int T[Maxn+5];
Node A[Maxn+5];
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("dream.in","r",stdin);
freopen("dream.out","w",stdout);
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
scanf("%d %d",&A[i].l,&A[i].r);
for(int i=1;i<=M;i++)
scanf("%d",&T[i]);
sort(A+1,A+N+1),sort(T+1,T+M+1);
priority_queue<int,vector<int>,greater<int> > q;
int ans=0;
for(int i=1,j=1;i<=M;i++) {
while(j<=N&&A[j].l<=T[i])
q.push(A[j].r),j++;
while(!q.empty()&&q.top()<T[i])
q.pop();
if(!q.empty()&&q.top()>=T[i])
ans++,q.pop();
}
printf("%d\n",ans);
return 0;
}
线段树优化建图网络流
待填坑
C.树
题目
题目描述
有一棵n个节点的无根树,给出其中的m对点对<x,y>。问有多少条树上的简单路径<u,v>满足该路径上不存在任何一对给出的点对<x,y>。
这里我们认为路径<u,v>和<v,u>是相同的。并且对于题目中给出的点对<x,y>满足x!=y,对于你要计数的路径<u,v>满足u!=v(即单点不算答案)。
输入
第一行两个正整数n,m。
接下来n-1行每行两个正整数u,v描述树上的一条边。
接下来m行每行两个正整数x,y描述一对给出的点对。
(注意,这里我们不保证同样的点对<x,y>不会重复出现)
输出
一行一个整数,表示满足要求的树上简单路径的条数。
样例输入
8 3
1 2
1 3
4 8
2 4
2 5
3 6
3 7
2 3
4 8
6 7
样例输出
11
数据范围
样例解释
满足条件的路径为<1,2>,< 1,3 >,< 1,4 >,< 1,5 >,< 1,6 >,< 1,7 >,< 2,4 >,< 2,5 >,< 3,6 >,< 3,7 >,< 4,5 >。
分析
我们发现直接计算合法路径似乎有点困难,所以我们考虑计算不合法路径。
对于菊花图的情况,不难发现只要有了点对< 1,u >,就相当于把u删了,有了点对< u,v >,就相当于多了一个不合法的路径。
对于一条链,一个限制< u,v >就相当于在这个DFS序列上,不合法的路径的一端落在 [ 1 , u ] [1,u] [1,u],另一端落在 [ v , N ] [v,N] [v,N]的一个序列,则问题转化为求不合法方案数的并集。若我们把 [ 1 , u ] [1,u] [1,u]看做平行于 x x x轴上的一条线段, [ v , N ] [v,N] [v,N]看做平行于 y y y轴的线段,这些不合法的点对数量就是这个矩形的面积。这是个二维的限制,我们就可以利用扫描线+线段树解决这个问题。
那么我们考虑用DFS序解决掉这个问题。若两个节点不是祖先-后代的关系,那么不合法的路径的起点和终点必须分别在两棵子树内部。否则若 u u u为祖先, v v v为后代,那么不合法路径的起点和终点分别是 v v v子树内部节点和 u u u子树中除 v v v子树的其他节点。
接下来套用链上的做法即可。
参考代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int Maxn=100000;
const int Maxlogn=18;
int N,M;
struct SegmentTree {
struct Segment {
int val,tag;
};
Segment t[Maxn*8+5];
void modify(int rt,int l,int r,int ml,int mr,int val) {
if(ml<=l&&r<=mr) {
t[rt].tag+=val;
if(t[rt].tag)t[rt].val=r-l+1;
else if(l==r)t[rt].val=0;
else t[rt].val=t[rt<<1].val+t[rt<<1|1].val;
return;
}
int mid=(l+r)>>1;
if(ml<=mid)modify(rt<<1,l,mid,ml,mr,val);
if(mr>=mid+1)modify(rt<<1|1,mid+1,r,ml,mr,val);
if(t[rt].tag)t[rt].val=r-l+1;
else if(l==r)t[rt].val=0;
else t[rt].val=t[rt<<1].val+t[rt<<1|1].val;
}
};
SegmentTree tr;
struct Matrix {
int line,y1,y2;
int val;
bool operator < (const Matrix &rhs) const {return line<rhs.line;}
};
vector<Matrix> t;
void add_matrix(int x1,int x2,int y1,int y2) {
t.push_back(Matrix{x1,y1,y2,1});
t.push_back(Matrix{x2+1,y1,y2,-1});
}
struct Edge {
int to;
Edge *nxt;
};
Edge pool[Maxn*2+5];
Edge *ecnt=&pool[0];
Edge *G[Maxn+5];
void addedge(int u,int v) {
Edge *p=++ecnt;
p->to=v;
p->nxt=G[u],G[u]=p;
}
int dep[Maxn+5],fa[Maxn+5][Maxlogn+5];
int dfn[Maxn+5],fir[Maxn+5],las[Maxn+5],cnt;
void dfs(int u,int f,int depth) {
dfn[++cnt]=u,fir[u]=cnt;
dep[u]=depth,fa[u][0]=f;
for(int i=1;i<=Maxlogn;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(Edge *p=G[u];p!=NULL;p=p->nxt) {
int v=p->to;
if(v==f)continue;
dfs(v,u,depth+1);
}
las[u]=cnt;
}
int calc_lca(int u,int v) {
for(int i=Maxlogn;i>=0;i--)
if(dep[fa[v][i]]>dep[u])
v=fa[v][i];
return v;
}
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d %d",&N,&M);
for(int i=1;i<N;i++) {
int u,v;
scanf("%d %d",&u,&v);
addedge(u,v);
addedge(v,u);
}
dfs(1,-1,1);
for(int i=1;i<=M;i++) {
int u,v;
scanf("%d %d",&u,&v);
if(fir[u]>fir[v])swap(u,v);
if(fir[v]<=las[u]&&fir[v]>fir[u]) {
int lca=calc_lca(u,v);
if(fir[lca]>1)add_matrix(1,fir[lca]-1,fir[v],las[v]);
if(las[lca]<N)add_matrix(fir[v],las[v],las[lca]+1,N);
} else add_matrix(fir[u],las[u],fir[v],las[v]);
}
sort(t.begin(),t.end());
long long ans=1LL*N*(N-1)/2;
for(int i=1,j=0;i<=N;i++) {
while(j<(int)t.size()&&t[j].line<=i)
tr.modify(1,1,N,t[j].y1,t[j].y2,t[j].val),j++;
ans-=tr.t[1].val;
}
printf("%lld\n",ans);
return 0;
}