CTSC2016 NOIP十合一 题解

CTSC2016 NOIP十合一 题解

$\text{Description}$

给定一张边带权有向图与模数,多次询问一个点到另一个点间距离为某个值的路径的条数对给定模数取模的值。

注意一个细节,即任意点到其自身存在一条长度为 $0$ 的“道路”。

提交答案题。

$\text{Solution}$

C1

性质

形如下图:

graph _7_.png

模数为 $2^{12}\times 3^8$。

什么,你说懒得打质因数分解?百度质因数分解不谢。

做法

设当前询问为 $u,v,w$。

每一个奇数到下一个偶数的路径长为 $1$,每一个偶数到下一个奇数的路径长为 $0$。

若 $u$ 为偶数,我们强制其退回上一个奇数,然后执行 $w\gets w-1$。

若 $v$ 为偶数,我们强制其前往下一个奇数。

那么现在 $u,v$ 都为奇数,我们考虑从某个奇数跳往下一个奇数时,有两种方案可选,一种花去 $1$ 长度,另一种不花去长度。

设 $l$ 表示从 $u$ 跳到 $v$ 要跳多少次,注意此处 $w$ 是更新过的。

那么我们需要计算的是:

这个模数不是质数,得想个方法快速计算组合数。

然后你发现一波扩展卢卡斯定理(exLucas)解决了?

扩展卢卡斯定理

递归切题法,启动!

细节判一下就能过了,时间复杂度 1s 跑的完(

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<bits/stdc++.h>
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

int n,m,q,p;

inline int Pow(int a,int b,int p){
int ans(1);
while(b){
if(b&1) ans=ans*a%p;
a=a*a%p,b>>=1;
}
return ans;
}

inline void exgcd(int a,int b,int& x,int& y){b?(exgcd(b,a%b,y,x),y-=a/b*x):(x=1,y=0);}

int _InvX,_InvY;
int Inv(int a,int P){
_InvY=_InvX=0;
exgcd(a,P,_InvX,_InvY);
return (_InvX%P+P)%P;
}

const int P1=1<<12,P2=Pow(3,8,100005);

int _2Mul[10005],_3Mul[10005];
int _2Sig[50005],_3Sig[50005];
int _2Phi[50005],_3Phi[50005];

inline void Init(){
_2Mul[0]=_3Mul[0]=1;
for(REG int i=1;i<P1;++i) _2Mul[i]=_2Mul[i-1]*(i%2?i:1)%P1;
for(REG int i=1;i<P2;++i) _3Mul[i]=_3Mul[i-1]*(i%3?i:1)%P2;
_2Phi[0]=_3Phi[0]=1;
for(REG int i=1;i<=50000;++i)
_2Phi[i]=_2Phi[i/2]*_2Mul[i%P1]%P1*Pow(_2Mul[P1-1],i/P1,P1)%P1,
_3Phi[i]=_3Phi[i/3]*_3Mul[i%P2]%P2*Pow(_3Mul[P2-1],i/P2,P2)%P2;
_2Sig[0]=_3Sig[0]=0;
for(REG int i=1;i<=50000;++i) _2Sig[i]=_2Sig[i/2]+i/2,_3Sig[i]=_3Sig[i/3]+i/3;
}

inline int Binom(int n,int m){
if(n<m) return 0;
int Pw=_2Sig[n]-_2Sig[m]-_2Sig[n-m];
int _2Ans,_3Ans;
if(Pw>=12) _2Ans=0;
else _2Ans=_2Phi[n]*Inv(_2Phi[m],P1)%P1*Inv(_2Phi[n-m],P1)%P1*Pow(2,Pw,P1)%P1;
Pw=_3Sig[n]-_3Sig[m]-_3Sig[n-m];
if(Pw>=8) _3Ans=0;
else _3Ans=_3Phi[n]*Inv(_3Phi[m],P2)%P2*Inv(_3Phi[n-m],P2)%P2*Pow(3,Pw,P2)%P2;
return (1ll*_2Ans*P2%p*Inv(P2,P1)%p+1ll*_3Ans*P1%p*Inv(P1,P2)%p)%p;
}

inline void Work(){
Init();
read(n),read(m),read(q),read(p);
for(REG int i=1;i<=m;++i){
int u,v,w;
read(u),read(v),read(w);
}
while(q--){
int u,v,w;
read(u),read(v),read(w);
if(u==v){printf("%d\n",!w);continue;}
if(!(v&1)) --w,--v;u|=1;
if(w<0){puts("0");continue;}
int Len=(v-u)>>1;
printf("%d\n",Binom(Len,w));
}
}

int main(){Work();}

点评

强行扩展卢卡斯,差评(

C2

性质

从点 $1$ 到点 $100$ 顺次相连形成一条链,且每个点上有一个自环。

询问中的 $u$ 都为 $1$,$w$ 不超过 $50000$。

做法

从一个数到其后继不需要花费边权。

我们可以任意挑选自环(可以重复挑选同一个自环),使得所选择自环上的边权和为 $w$,然后求方案数。

完全背包求方案数?

由于模数的问题,我们老老实实做 DP 就可以了,注意状态设计时不能压掉前面那一维。

都做这题了还不会完全背包?不会吧不会吧不会吧?好吧我不会……

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h>
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=19960928;

int n,m,q,p;

int u,v,w;

int Wht[105];

int DP[105][50005];

inline void Init(){
DP[0][0]=1;
for(REG int i=1;i<=n;++i)
for(REG int j=0;j<=50000;++j){
DP[i][j]=DP[i-1][j];
if(j>=Wht[i]) DP[i][j]=(DP[i][j]+DP[i][j-Wht[i]])%Mod;
}
}

inline void Work(){
read(n),read(m),read(q),read(p);
while(m--){
read(u),read(v),read(w);
if(u==v) Wht[u]=w;
}
Init();
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
printf("%d\n",DP[v][w]);
}
}

int main(){Work();}

点评

好像没啥好说的……

C3

性质

从点 $1$ 到点 $10^4$ 顺次相连形成一条链,且每个点上有一个自环。

询问中 $w$ 不超过 $1000$。

模数 $1998585857=953\times2^{21}+1$,其原根为 $3$。

做法

跟 C2 差不多,但是数据范围上有从量到质的变化。

如果直接 DP,那么 C3 会跑死,这时候就要祭出多项式了。

设第 $i$ 个物品的生成函数为 $F_i(x)$,其重量为 $w_i$,那么它的生成函数是很容易写出来的(除了 $w_i$ 的倍数上有 $1$ 其它都为 $0$,注意这边认为 $0$ 也是 $w_i$ 的倍数)。

每次询问我们只需求出某一个区间内所有物品的生成函数的乘积。

注意到询问中 $w$ 不超过 $1000$,故多项式最多只有 $1001$ 项。

预处理前缀生成函数乘积与其逆即可。

注意 $1998585857$ 加起来会爆 int。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include<bits/stdc++.h>
#define int long long
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=1998585857,lim=2048,L=11,G=3,InvG=666195286;

namespace Polynomial{
#define MAXN 50005

inline void Swap(int& a,int& b){int t=a;a=b,b=t;}
inline int Add(int a,int b){return (a+=b)>=Mod?a-Mod:a;}
inline int Del(int a,int b){return (a-=b)<0?a+Mod:a;}
inline int Min(int a,int b){return a<b?a:b;}

inline int Pow(int a,int b){
int ans(1);
while(b){
if(b&1) ans=1ll*ans*a%Mod;
a=1ll*a*a%Mod,b>>=1;
}
return ans;
}

int NumInv[MAXN];
int PosSt[55],NegSt[55];

inline void Predone(){
NumInv[1]=1;
for(REG int i=2;i<=MAXN-5;++i) NumInv[i]=1ll*(Mod-Mod/i)*NumInv[Mod%i]%Mod;
for(REG int i=1;i<=30;++i) PosSt[i]=Pow(3,(Mod-1)/(1<<i)),NegSt[i]=Pow(PosSt[i],Mod-2);
}

int Rev[MAXN];

inline void GetRev(int len,int L){
for(REG int i=1;i<len;++i)
Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(L-1));
}

inline void NTT(int* A,int len,short op){
for(REG int i=1;i<len;++i)
if(i<Rev[i]) Swap(A[i],A[Rev[i]]);
for(REG int i=1,j=1;i<len;i<<=1,++j){
int Stp=op?PosSt[j]:NegSt[j];
for(REG int j=0;j<len;j+=(i<<1)){
int Tmp=1;
for(REG int k=0;k<i;++k,Tmp=1ll*Tmp*Stp%Mod){
int x=*(A+j+k),y=1ll*Tmp*(*(A+j+k+i))%Mod;
*(A+j+k)=Add(x,y);
*(A+j+k+i)=Del(x,y);
}
}
}
if(!op){
int Invlen=NumInv[len];
for(REG int i=0;i<len;++i) A[i]=1ll*A[i]*Invlen%Mod;
}
}

int MulA[MAXN],MulB[MAXN];

inline void Mul(int* A,int* B,int* C,int len){
memset(MulA,0,sizeof(MulA)),memset(MulB,0,sizeof(MulB));
for(REG int i=0;i<(len>>1);++i)
MulA[i]=A[i],MulB[i]=B[i];
NTT(MulA,len,1),NTT(MulB,len,1);
for(REG int i=0;i<len;++i)
MulA[i]=1ll*MulA[i]*MulB[i]%Mod;
NTT(MulA,len,0);
for(REG int i=0;i<len;++i) C[i]=MulA[i];
}

int InvB[2][MAXN],InvCur;
int InvTmpG[MAXN];

inline void Inv(int* A,int* B,int len){
InvCur=0,memset(InvB,0,sizeof(InvB)),memset(InvTmpG,0,sizeof(InvTmpG));
int Now=1,Nxt=2,NxtL=1;
InvB[0][0]=Pow(A[0],Mod-2);
GetRev(Nxt,NxtL);
while(Now<=(len<<1)){
InvCur^=1,memset(InvB[InvCur],0,sizeof(InvB[InvCur]));
for(REG int i=0;i<Now;++i) InvB[InvCur][i]=Add(InvB[InvCur^1][i]<<1,0);
for(REG int i=0;i<Now;++i) InvTmpG[i]=InvB[InvCur^1][i];
for(REG int i=0;i<Now;++i) InvB[InvCur^1][i]=A[i];
NTT(InvTmpG,Nxt,1),NTT(InvB[InvCur^1],Nxt,1);
for(REG int i=0;i<Nxt;++i) InvB[InvCur^1][i]=1ll*InvB[InvCur^1][i]*InvTmpG[i]%Mod;
for(REG int i=0;i<Nxt;++i) InvB[InvCur^1][i]=1ll*InvB[InvCur^1][i]*InvTmpG[i]%Mod;
NTT(InvB[InvCur^1],Nxt,0);
for(REG int i=0;i<Now;++i) InvB[InvCur][i]=Del(InvB[InvCur][i],InvB[InvCur^1][i]);
Now<<=1,Nxt<<=1,++NxtL;
if(Now<=(len<<1)) GetRev(Nxt,NxtL);
}
for(REG int i=0;i<=len;++i) B[i]=InvB[InvCur][i];
}

inline void Int(int* A,int* B,int len){for(REG int i=1;i<=len;++i) B[i]=1ll*A[i-1]*Pow(i,Mod-2)%Mod;B[0]=0;}
inline void Der(int* A,int* B,int len){for(REG int i=1;i<=len;++i) B[i-1]=1ll*i*A[i]%Mod;B[len]=0;}

int LnDer[MAXN],LnInv[MAXN];

inline void Ln(int* A,int* B,int len){
Der(A,LnDer,len),Inv(A,LnInv,len);
int lim=1,L=0;
while(lim<=(len<<1)) lim<<=1,++L;
GetRev(lim,L);
Mul(LnDer,LnInv,LnDer,lim);
Int(LnDer,B,len);
}

int ExpB[2][MAXN],ExpCur,ExpLn[MAXN];

inline void Exp(int* A,int* B,int len){
ExpCur=0,memset(ExpB,0,sizeof(ExpB));
int Now=1,Nxt=2,NxtL=1;
ExpB[0][0]=1;
GetRev(Nxt,NxtL);
while(Now<=(len<<1)){
ExpCur^=1,memset(ExpB[ExpCur],0,sizeof(ExpB[ExpCur]));
Ln(ExpB[ExpCur^1],ExpLn,Now-1);
for(REG int i=0;i<Now;++i) ExpLn[i]=Del(A[i]+(i==0),ExpLn[i]);
Mul(ExpB[ExpCur^1],ExpLn,ExpB[ExpCur],Nxt);
Now<<=1,Nxt<<=1,++NxtL;
if(Now<=(len<<1)) GetRev(Nxt,NxtL);
}
for(REG int i=0;i<=len;++i) B[i]=ExpB[ExpCur][i];
}

inline void PolyPow(int* A,int* B,int k,int len){
Ln(A,B,len);
for(REG int i=0;i<=len;++i) B[i]=1ll*B[i]*k%Mod;
Exp(B,B,len);
}
}

using namespace Polynomial;

int n,m,q,p;

int u,v,w;

int Poly[10005][2505],Ioly[10005][2505];

int Tmpa[3005],Tmpb[3005];

inline void Work(){
Predone();
freopen("noip3.in","r",stdin);
freopen("noip3.out","w",stdout);
read(n),read(m),read(q),read(p);
Poly[0][0]=Ioly[0][0]=1;
for(REG int i=1;i<=m;++i){
read(u),read(v),read(w);
if(u==v){
GetRev(lim,L);
for(REG int j=0;j<=1000;j+=w) Poly[u][j]=1;
for(REG int j=0;j<lim;++j) Tmpa[j]=Poly[u-1][j];
NTT(Tmpa,lim,1),NTT(Poly[u],lim,1);
for(REG int j=0;j<2048;++j) Poly[u][j]=1ll*Tmpa[j]*Poly[u][j]%Mod;
NTT(Poly[u],lim,0);
for(REG int j=1001;j<=2048;++j) Poly[u][j]=0;
Inv(Poly[u],Ioly[u],1000);
}
}
GetRev(lim,L);
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
memset(Tmpa,0,sizeof Tmpa);
memset(Tmpb,0,sizeof Tmpb);
for(REG int j=0;j<=1000;++j) Tmpa[j]=Poly[v][j],Tmpb[j]=Ioly[u-1][j];
NTT(Tmpa,lim,1),NTT(Tmpb,lim,1);
for(REG int j=0;j<lim;++j) Tmpa[j]=1ll*Tmpa[j]*Tmpb[j]%Mod;
NTT(Tmpa,lim,0);
printf("%d\n",Tmpa[w]);
if(i==1) cerr<<Tmpa[w]<<endl;
}
}

signed main(){Work();}

点评

啥也不说了,$998244353$ 天下第一!!

C4

性质

$n=5$,边权较小的边重复出现次数较多,询问中 $w$ 不超过 $50000$。

做法

这个 $n$ 很小,开数据时想这个点应该是个暴力点?

然而边数过多,没想到较好的暴力,故考虑 DP。

设 $f(u,v,w)$ 表示从 $u$ 到 $v$ 边权和为 $w$ 的路径数,易有转移:

时间复杂度 $\Theta(n^2w+nwm)$(考虑每条边在转移时贡献的次数),暴毙。

再回顾本题性质:

  1. 可能会有部分状态是无用的。
  2. 有些边的出现次数较多。

我们考虑换一个 DP 方式,首先对每类起始点相同的边按边权从小到大排序,然后按顺序枚举状态(当然你需要初始化:每个点到它自身有一条边权和为 $0$ 的路径),若其非零,则用 $v$ 的出边更新别的状态,对于我们所更新的这个状态,若其 $w$ 超过 $50000$,则跳过,顺便可以跳过同一个终点的更新,因为我们已经进行了排序。

注意这个模数仍然是 C3 的 $1998585857$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<bits/stdc++.h>
#define int long long
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=1998585857;

int n,m,q,p;
int u,v,w;

vector< pair<int,int> > Tmp[10][10],Edge[10][10];

int DP[10][10][50005];

inline void Init(){
read(n),read(m),read(q),read(p);
for(REG int i=1;i<=m;++i){
read(u),read(v),read(w);
Tmp[u][v].push_back(make_pair(w,0));
}
for(REG int i=1;i<=5;++i)
for(REG int j=1;j<=5;++j){
sort(Tmp[i][j].begin(),Tmp[i][j].end());
int cnt=1;
for(REG int k=1;k<Tmp[i][j].size();++k)
if(Tmp[i][j][k].first^Tmp[i][j][k-1].first) Edge[i][j].push_back(make_pair(Tmp[i][j][k-1].first,cnt)),cnt=1;
else ++cnt;
Edge[i][j].push_back(make_pair(Tmp[i][j][Tmp[i][j].size()-1].first,cnt));
}
for(REG int i=1;i<=5;++i) DP[i][i][0]=1;
for(REG int i=0;i<=49999;++i)
for(REG int ui=1;ui<=5;++ui)
for(REG int vi=1;vi<=5;++vi){
if(!DP[ui][vi][i]) continue;
for(REG int ki=1;ki<=5;++ki){
for(REG int mi=0;mi<Edge[vi][ki].size();++mi){
int wi=Edge[vi][ki][mi].first,ci=Edge[vi][ki][mi].second;
if(i+wi>50000) break;
DP[ui][ki][i+wi]=(DP[ui][ki][i+wi]+DP[ui][vi][i]*ci%Mod)%Mod;
}
}
}
}

inline void Work(){
Init();
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
printf("%d\n",DP[u][v][w]);
}
}

signed main(){Work();}

点评

跑一次 1 min,酸爽。

C5

性质

图由若干个三元链组成。每个三元链的中心是 $11\sim50000$ 节点,两端是 $1\sim 10$ 节点。

边权不超过 $200$,询问中的 $w$ 不超过 $10000$。

做法

容易发现任意 $11\sim 50000$ 节点都只有一个前驱和一个后缀,故询问时可以强制其跳到它的前驱/后缀($1\sim 10$ 节点跳到其本身即可)。

所以一个三元链可以等价为 $1\sim 10$ 节点间的连边,可以采用 C4 的做法。

哦,注意这个模数仍然是 C4 的 $1998585857$(不过直接搬 C4 代码应该没有这个问题)。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
#define int long long
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=1998585857;

int n,m,q,p;
int u,v,w;

vector< pair<int,int> > Tmp[11][11],Edge[11][11];

int DP[11][11][10005];

int Pre[50005],Suf[50005],Pw[50005],Sw[50005];

inline void Init(){
read(n),read(m),read(q),read(p);
for(REG int i=1;i*2<=m;++i){
int sumw,ru,rv;
read(u),read(v),read(w);
sumw=w,ru=u,Pre[v]=u,Pw[v]=w;
read(u),read(v),read(w);
rv=v,sumw+=w,Suf[u]=v,Sw[u]=w;
Tmp[ru][rv].push_back(make_pair(sumw,0));
}
for(REG int i=1;i<=10;++i) Pre[i]=Suf[i]=i;
for(REG int i=1;i<=10;++i)
for(REG int j=1;j<=10;++j){
sort(Tmp[i][j].begin(),Tmp[i][j].end());
int cnt=1;
for(REG int k=1;k<Tmp[i][j].size();++k)
if(Tmp[i][j][k].first^Tmp[i][j][k-1].first) Edge[i][j].push_back(make_pair(Tmp[i][j][k-1].first,cnt)),cnt=1;
else ++cnt;
Edge[i][j].push_back(make_pair(Tmp[i][j][Tmp[i][j].size()-1].first,cnt));
}
for(REG int i=1;i<=10;++i) DP[i][i][0]=1;
for(REG int i=0;i<=9999;++i)
for(REG int ui=1;ui<=10;++ui)
for(REG int vi=1;vi<=10;++vi){
if(!DP[ui][vi][i]) continue;
for(REG int ki=1;ki<=10;++ki){
for(REG int mi=0;mi<Edge[vi][ki].size();++mi){
int wi=Edge[vi][ki][mi].first,ci=Edge[vi][ki][mi].second;
if(i+wi>10000) break;
DP[ui][ki][i+wi]=(DP[ui][ki][i+wi]+DP[ui][vi][i]*ci%Mod)%Mod;
}
}
}
}

inline void Work(){
Init();
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
w-=Sw[u]+Pw[v],u=Suf[u],v=Pre[v];
if(w<0) puts("0");
else printf("%d\n",DP[u][v][w]);
}
}

signed main(){Work();}

点评

什么叫做 10s 跑完啊?(战术后仰)

C6

性质

$n=200$,边权全为 $1$。

做法

矩阵快速幂?但是询问中的 $w$ 互不相同,朴素地做时间复杂度为 $\Theta(qn^3\log w)$,这可不能像 C4,C5 那样暴力跑 1min/10s 能解决的。

注意到我们每次只需要求出矩阵的一个点,那么我们预处理邻接矩阵的 $2^0,2^1,\cdots,2^{30}$ 的幂,每次询问通过二进制分解不断将预处理出来的矩阵乘到从单位矩阵拎出来一列所形成的列向量上。

时间复杂度 $\Theta(n^3\log w+qn^2\log w)$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<bits/stdc++.h>
#define int long long
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=1998585857;

int n,m,q,p;
int u,v,w;

int Vec[305],Tmp[305];

struct Matrix{
int M[305][305];
inline void Init(){memset(M,0,sizeof M);}
inline void GetI(){for(REG int i=1;i<=n;++i)M[i][i]=1;}
Matrix operator*(Matrix x){
Matrix R;R.Init();
for(REG int i=1;i<=n;++i)
for(REG int k=1;k<=n;++k)
for(REG int j=1;j<=n;++j)
R.M[i][j]=(R.M[i][j]+M[i][k]*x.M[k][j]%Mod)%Mod;
return R;
}
Matrix MulT(){
memset(Tmp,0,sizeof Tmp);
for(REG int j=1;j<=n;++j)
for(REG int i=1;i<=n;++i)
Tmp[i]=(Tmp[i]+Vec[j]*M[j][i]%Mod)%Mod;
for(REG int i=1;i<=n;++i) Vec[i]=Tmp[i];
}
}_2PowM[36];

inline void Init(){
_2PowM[0].Init();
for(REG int i=1;i<=m;++i)
read(u),read(v),read(w),_2PowM[0].M[u][v]++;
for(REG int i=1;i<=29;++i) _2PowM[i]=_2PowM[i-1]*_2PowM[i-1];
}

inline void Work(){
read(n),read(m),read(q),read(p);
Init();
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
memset(Vec,0,sizeof Vec),Vec[u]=1;
for(REG int j=0;j<=29;++j)
if(w&(1<<j)) _2PowM[j].MulT();
printf("%lld\n",Vec[v]);
}
}

signed main(){Work();}

点评

算是矩阵快速幂比较进阶的应用姿势?跑了 300s

C7

性质

$n=100$,有 $100$ 条边的边权为 $2$,其它边权全为 $1$。

做法

一开始看到第一条边的边权还以为我手贱改了啥……然后又下载了一次数据发现没有错……

所以本题的核心在于处理多出来的边权为 $2$ 的边

然后对这个边权为 $2$ 的边建一个新点,变成两条边权为 $1$ 的边?

套用 C6 的做法即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<bits/stdc++.h>
#define int long long
#define REG register
using namespace std;
inline void read(int& x){
static char c;
while(!isdigit(c=getchar()));x=c^48;
while(isdigit(c=getchar()))x=(x*10)+(c^48);
}

const int Mod=1998585857;

int n,m,q,p;
int u,v,w;

int Vec[305],Tmp[305];

struct Matrix{
int M[305][305];
inline void Init(){memset(M,0,sizeof M);}
inline void GetI(){for(REG int i=1;i<=n;++i)M[i][i]=1;}
Matrix operator*(Matrix x){
Matrix R;R.Init();
for(REG int i=1;i<=n;++i)
for(REG int k=1;k<=n;++k)
for(REG int j=1;j<=n;++j)
R.M[i][j]=(R.M[i][j]+M[i][k]*x.M[k][j]%Mod)%Mod;
return R;
}
Matrix MulT(){
memset(Tmp,0,sizeof Tmp);
for(REG int j=1;j<=n;++j)
for(REG int i=1;i<=n;++i)
Tmp[i]=(Tmp[i]+Vec[j]*M[j][i]%Mod)%Mod;
for(REG int i=1;i<=n;++i) Vec[i]=Tmp[i];
}
}_2PowM[36];

inline void Init(){
_2PowM[0].Init();
for(REG int i=1;i<=m;++i){
read(u),read(v),read(w);
if(w==1){_2PowM[0].M[u][v]++;continue;}
_2PowM[0].M[u][++n]++,_2PowM[0].M[n][v]++;
}
for(REG int i=1;i<=29;++i) _2PowM[i]=_2PowM[i-1]*_2PowM[i-1];
}

inline void Work(){
read(n),read(m),read(q),read(p);
Init();
for(REG int i=1;i<=q;++i){
read(u),read(v),read(w);
memset(Vec,0,sizeof Vec),Vec[u]=1;
for(REG int j=0;j<=29;++j)
if(w&(1<<j)) _2PowM[j].MulT();
printf("%lld\n",Vec[v]);
}
}

signed main(){Work();}

点评

基操。又颓了 6min 知乎,然后次日的语文卷子没写

C8

性质

$n=1000$,边权全为 $1$,询问中的 $u$ 都为 $1$。

做法

这个数据范围很棘手,要不先套 C6 的做法试试?

开始跑……跑不动了,关掉,虽然只跑了一小会。

然后发现特征多项式那一套不是很会,又看到有人说跑一小时,心想:一小时就一小时吧。

结果 1023s 出结果?

点评

暴力出奇迹!

C9

性质

$n=10000$,边权全为 $1$,边 $(u,v)$ 满足 $u\equiv v+1\pmod{10^3}$,对于所有询问 $(a,b,c)$,满足 $b-a\equiv c\pmod{10^3}$。

做法

用 C6 的做法是不可能的,空间会卡爆。

考虑边和询问的特点,如果我们将点分为 $10^3$ 类,第 $i$ 类表示编号对 $10^3$ 取模后值为 $i$ 的点,那么边相当于一个类往其后继类连边,询问则是为了使得在边的限制下存在解而限制的,且我们可以得出在这些类中走了多少圈。

那么设出矩阵 $G_i$ 表示第 $i$ 类点到第 $i+1$ 类点的邻接矩阵,那么实际上是对应的行向量依次乘上 $G_{u\bmod{10^3}},G_{(u\bmod{10^3})+1},\cdots,G_{v\bmod{10^3}}$。

预处理 $G_0\times G_1\times\cdots\times G_{10^3-1}$,然后上式完整的所有类的乘积可以直接套用 C6 的做法解决,其它暴力即可。

点评

3min 跑完,挺快的。

C10

性质

$n=3000$,边权全为 $1$。

做法

不能 #define int long long 了,否则空间卡爆。

继续套用 C6 的做法,暴力出奇迹。

然后开着跑了很久很久……

点评

从八点半跑到了十点半,虽然等得很辛苦,但是一想到这段时间划了很久水,顺便写了 R2A 这算给 QkOI#R2 打广告吗 的相关内容,甚至不用去写特征多项式等神仙玩意儿,我便感觉,暴力出奇迹的生活,是多么的朴实无华,且枯燥。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×