100+40+0=140。暴力没写满……

简单模拟

很久很久以前,有一个 \(1\sim n\) 的排列 \(a\),还有一个长度为 \(q\) 的,每个元素在 \(1\) 到 \(n\) 之间的序列 \(b_0,\dots,b_{q-1}\)。

作为一道简单模拟题,你需要模拟 \(m\) 次操作,第 \(i\)(\(1\) 到 \(m\))次操作你会在 \(a\) 中找到值为 \(b_{(i-1)\bmod q}\) 的元素,并把它与第一个元素交换。

你只需要输出 \(m\) 次操作之后的序列即可。

\(1\leq n,q\leq 200000,1\leq m\leq 10^{18}\)。

题解

看位置对应的元素没有头绪,但是看元素所在的位置却有迹可循。

分析元素所在位置的变换,因为每次交换的是 \(b_{(i-1)\bmod q}\) 和第一个元素的位置,所以

  1. 当 \((i-1)\bmod q=0\) 时

    1. \(i=1\),交换的是元素 \(b_0\) 和元素 \(a_1\) 的位置。

    2. \(i>1\),交换的是元素 \(b_0\) 和元素 \(b_{q-1}\) 的位置。

  2. 当 \((i-1)\bmod q>0\),交换的是 \(b_{(i-1)\bmod q}\) 和 \(b_{(i-2)\bmod q}\) 的位置。

把操作 \(q\) 次看成一个周期,我们可以

  1. 暴力模拟特殊的第一个周期。

  2. 寻找普通的中间周期中元素位置变化的实质,即我们要找到每 \(q\) 次操作对应的置换。然后用置换的知识快速计算中间周期的结果。

  3. 暴力末尾不足一个周期的操作。

每 \(q\) 次操作对应的置换可以通过如下代码求得:

b[0]=b[q];
for(int i=1;i<=n;++i) per[i]=i;
for(int i=1;i<=q;++i) swap(per[b[i]],per[b[i-1]]);

那么per[i]表示的是元素 \(i\) 最终与元素per[i]交换位置了。即per[i]所在位置上现在应该填 \(i\)。

如何快速计算中间周期的结果呢?我们可以把每个轮换找出来,然后利用它的循环节进行操作。

时间复杂度 \(O(q)\)。

AC程序:

CO int N=200000+10;
int b[N],per[N];
int cir[N],len;
bool vis[N];
int ans[N],pos[N]; int main(){
freopen("sim.in","r",stdin),freopen("sim.out","w",stdout);
int n=read<int>(),q=read<int>();
LL m=read<LL>(); pos[b[0]=read<int>()]=1;
for(int i=2;i<=n;++i) pos[read<int>()]=i;
for(int i=1;i<=q;++i) read(b[i]); if(m<=q){
for(int i=1;i<=m;++i) swap(pos[b[i-1]],pos[b[i]]); for(int i=1;i<=n;++i) ans[pos[i]]=i;
for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
return 0;
}
m-=q;
for(int i=1;i<=q;++i) swap(pos[b[i-1]],pos[b[i]]); b[0]=b[q];
for(int i=1;i<=n;++i) per[i]=i;
for(int i=1;i<=q;++i) swap(per[b[i]],per[b[i-1]]);
for(int i=1;i<=n;++i)if(!vis[i]){
vis[i]=1,len=0,cir[len++]=i;
for(int j=per[i];j!=i;j=per[j]) vis[j]=1,cir[len++]=j;
int rest=m/q%len;
for(int j=0;j<len;++j) per[cir[(j+rest)%len]]=cir[j];
}
for(int i=1;i<=n;++i) ans[pos[i]]=per[i]; for(int i=1;i<=n;++i) pos[ans[i]]=i;
for(int i=1;i<=m%q;++i) swap(pos[b[i]],pos[b[i-1]]); for(int i=1;i<=n;++i) ans[pos[i]]=i;
for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
return 0;
}

暴力程序:

CO int N=200000+10;
int pos[N],b[N],ans[N]; int main(){
int n=read<int>(),q=read<int>();
LL m=read<LL>(); pos[b[0]=read<int>()]=1;
for(int i=2;i<=n;++i) pos[read<int>()]=i;
for(int i=1;i<=q;++i) read(b[i]); if(m<=q){
for(int i=1;i<=m;++i) swap(pos[b[i-1]],pos[b[i]]); for(int i=1;i<=n;++i) ans[pos[i]]=i;
for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
return 0;
}
m-=q;
for(int i=1;i<=q;++i) swap(pos[b[i-1]],pos[b[i]]); b[0]=b[q];
for(int i=q+1;i<=m;++i) b[i]=b[i-q];
for(int i=1;i<=m;++i) swap(pos[b[i]],pos[b[i-1]]); for(int i=1;i<=n;++i) ans[pos[i]]=i;
for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
return 0;
}

std给的是倍增。现在想来确实倍增代码实现更简单,考场上我看到这题的数据范围应该优先去写倍增的。

简单构造

一次歌唱比赛中,一位歌手刚刚结束表演,评委正在打分。一共有 \(n\) 位评委, 他们每人可以打 \(1\) 分或 \(0\) 分,第 \(i\) 位评委希望歌手的得分为 \(v_i\)。

评委们有特殊的控分技巧,他们会按一个顺序依次评分,第一个评分的评委会不管三七二十一打 \(0\) 分。对于接下来的评委,假设前面 \(a\) 位评委评分总和为 \(b\),评委会认为这位歌手期望得分为 \(\frac{b}{a}n\),如果这个得分低于他所希望的得分,他会打 \(1\) 分,否则他会打 \(0\) 分。

作为最大的黑幕——裁判,你对这一切心知肚明。你希望选手的得分为 \(p~(0\leq p\leq n)\),为此你可以调换评委们的评分顺序。你需要输出一个 \(1\sim n\) 的排列,第 \(i\) 个位置表示第 \(i\) 个评分的裁判的编号,让选手的得分最接近 p。如果有多种,你只需要输出任意一种。

  • Subtask 1,10pts,\(1\leq n\leq 10\)。
  • Subtask 2,20pts,\(1\leq n\leq 100\)。
  • Subtask 3,30pts,\(1\leq n\leq 1000\)。
  • Subtask 4,10pts,\(1\leq n\leq 10^5,p=0\)。
  • Subtask 5,30pts,\(1\leq n\leq 10^5\)。

暴力

我当时认为裁判要努力让期望得分最大的 \(p\) 个评委打 \(1\) 分,让剩余 \(n-p\) 个评委打 \(0\) 分。

然后思考了一下 \(p=0\) 和 \(p=n\) 的情况,发现那 \(p\) 个期望得分大的评委应该从小到大排序,\(n-p\) 个小的评委应该从大到小排序。

设计状态 \(f(i,j,k)\) 表示大评委用了 \(i\) 个,小评委用了 \(j\) 个,得了 \(k\) 分的可行性。

暴力转移:

namespace T1{
CO int N=100+10;
pair<int,int> v[N],a[N],b[N];
int f[N][N][N];
pair<int,int> g[N][N][N]; int main(int n,int p){
for(int i=1;i<=n;++i) read(v[i].first),v[i].second=i;
sort(v+1,v+n+1);
for(int i=1;i<=p;++i) a[i]=v[n-p+i];
for(int i=1;i<=n-p;++i) b[i]=v[n-p+1-i];
f[1][0][0]=1,g[1][0][0]=make_pair(1,0);
f[0][1][0]=1,g[0][1][0]=make_pair(2,0);
for(int i=0;i<=p;++i)for(int j=0;j<=n-p;++j)if(i+j>=1)
for(int k=0;k<=i+j;++k)if(f[i][j][k]){
if(i<p){
int d=(LD)k/(i+j)*n<a[i+1].first;
f[i+1][j][k+d]=1,g[i+1][j][k+d]=make_pair(1,d);
}
if(j<n-p){
int d=(LD)k/(i+j)*n<b[j+1].first;
f[i][j+1][k+d]=1,g[i][j+1][k+d]=make_pair(2,d);
}
}
int delta=0;
while(!f[p][n-p][p+delta] and !f[p][n-p][p-delta]) ++delta;
int i=p,j=n-p,k=f[p][n-p][p+delta]?p+delta:p-delta;
vector<int> sol;
while(i or j){
if(g[i][j][k].first==1) sol.push_back(a[i].second),k-=g[i][j][k].second,--i;
else sol.push_back(b[j].second),k-=g[i][j][k].second,--j;
}
for(;sol.size();sol.pop_back()) printf("%d%c",sol.back()," \n"[sol.size()==1]);
}
}

然后我吸收别人CSP的经验,想着把决策打出来找规律。但是本身这个DP就是我臆想的,所以规律就是一个尽量调整……

现在想来我似乎可以将大小评委人数上下微调,说不定能过。

CSAcademy Voting

Let's make a key observation first: if we have two adjacent judges \(i\) and \(i+1\) with assessments \(x\) and \(y\), with \(x<y\), swapping them will either yield the same total score for the singer, or decrease it by one.

To prove this, let's denote as \(s\) the score as it is before the \(i\)th judge. Now, let's consider some cases:

  • their votes are \(1\) and \(1\): this means \(x\cdot (i-1) > n\cdot s\) and \(y\cdot i > n\cdot (s + 1)\). Since \(y \le n\), we get \(y\cdot (i-1) > ns\). At least one of these judges still puts \(1\).
  • votes are \(1\) and \(0\): this means \(x\cdot (i-1) > n\cdot s\) and \(y\cdot i \le n\cdot (s + 1)\). Since \(y > x\), we get \(x\cdot i \le n\cdot (s + 1)\). That is, if the first judge puts \(1\), the second judge puts \(0\). Anyway, at least one of them puts \(0\).
  • votes are \(0\) and \(1\): \(x\cdot (i-1) \le n\cdot s\) and \(y\cdot i > n\cdot s\). Since \(x \le n\), we get \(x\cdot i \le n\cdot (s + 1)\). As in the previous case, at least one of these judges puts \(0\).
  • votes are \(0\) and \(0\). \(x\cdot (i-1) \le n\cdot s\) and \(y\cdot i \le n\cdot s\). Obviously \(y\cdot (i-1) \le n\cdot s\). Since \(y > x\), we get \(x\cdot i \le n\cdot s\). Both judges still puts \(0\).

Now let's prove that the overall score either doesn't change or decreases by one. All judges before \(i\) vote the same way. If the sum of votes of swapped judges doesn't change, overall result also remains the same. Otherwise we find the first judge after \(i+1\) who changes his vote. If he doesn't exist, the result decreases by one. If he exists, he changes his vote from \(0\) to \(1\). All judges after him don't change their votes. The result doesn't change.

Let's see how we can use this observation to solve the problem. Focusing it towards extreme cases yields the following new observations:

  • the highest total score possible is obtained when judges are in increasing order of their assessment (call this \(M\))

  • the lowest total score possible is obtained when judges are in decreasing order of their assessment (call this \(m\))

  • any number between \(m\) and \(M\) can be obtained. (let's call this statement \(Q\))

We will investigate the third of these statements a bit further. Let's say the array of assessments of the judges is \(A_1, A_2\ ...\ A_n\), in increasing order. We will build a list \(\mathcal{P}\) of permutations of \(A\), say with \(k\) elements, respecting the following conditions:

  • \(\mathcal{P}_1\ = A_1\ A_2\ \ \ \ \ ...\ A_n\)

  • \(\mathcal{P}_k\ = A_n\ A_{n-1}\ ...\ A_1\)

  • For any \(i < k\), we can transform permutation \(\mathcal{P}_i\) into permutation \(\mathcal{P}_{i+1}\) by swapping a single pair of adjacent elements, \(\mathcal{P}_i[j]\) and \(\mathcal{P}_i[j+1]\) that initially were in increasing order (\(\mathcal{P}_i[j] < \mathcal{P}_i[j+1]\)).

An example of such a list, for \(A=[1, 2, 3]\) is:

\[\mathcal{P}_1\ = 1\ 2\ 3\\
\mathcal{P}_2\ = 1\ 3\ 2\\
\mathcal{P}_3\ = 3\ 1\ 2\\
\mathcal{P}_4\ = 3\ 2\ 1
\]

The special property of such a list is that the score of the singer for permutation \(\mathcal{P}_i\) will be either the same or one more than the score for \(\mathcal{P}_{i+1}\) (this is due to the observation above). The statement \(Q\) is proved by this: as the score for \(\mathcal{P}_1\) is \(M\), the score for \(\mathcal{P}_k\) is mm and the score decreases by at most \(1\) at every step, every number between \(m\) and \(M\) is achieved by at least one of the permutations in \(\mathcal{P}\). Also, if we denote by \(S_i\) the score for permutation \(\mathcal{P}_i\), \(S\) will be sorted in decreasing order.

If \(X\) is less than \(m\), the answer is \(\mathcal{P}_k\). If \(X\) is greater than \(M\), the answer is \(\mathcal{P}_1\). If \(X\) is between \(m\) and \(M\), we can binary search for a position in \(\mathcal{P}\) that yields exactly \(X\). Beware: you need to be able to compute only the permutations for indexes found through the binary search. You cannot pre-compute all permutations of \(\mathcal{P}\). So all that is left is to find a good way to construct the list \(\mathcal{P}\) that allows this. An easy example is continually taking the last element and moving it to the front:

\[\mathcal{P}_1\ = A_1\ A_2\ ...\ A_{n-2}\ A_{n-1}\ A_n\\
\mathcal{P}_2\ = A_1\ A_2\ ...\ A_{n-2}\ A_n\ A_{n-1}\\
\mathcal{P}_3\ = A_1\ A_2\ ...\ A_n\ A_{n-2}\ A_{n-1}\\
\vdots\\
\mathcal{P}_{n-1}\ = A_n\ A_1\ A_2\ ...\ A_{n-2}\ A_{n-1}\\
\mathcal{P}_{n}\ = A_n\ A_1\ A_2\ ...\ A_{n-1}\ A_{n-2}\\
\vdots\\
\mathcal{P}_{2n-2}\ = A_n\ A_{n-1}\ A_1\ ...\ A_{n-3}\ A_{n-2}\\
\vdots\\
\mathcal{P}_{k}\ = A_n\ A_{n-1}\ ...\ A_{2}\ A_{1}
\]

参考std的程序实现,构造的调整是不断地把第一个元素放到后面。

时间复杂度 \(O(n \log n)\)。

CO int N=100000+10;
int a[N],test[N],nw[N]; int calc(int n,int a[]){
int sum=0;
for(int i=2;i<=n;++i)
if((LD)sum/(i-1)*n<a[i]) ++sum;
return sum;
} int main(){
int n=read<int>(),p=read<int>();
for(int i=1;i<=n;++i) read(a[i]);
sort(a+1,a+n+1);
if(p>=calc(n,a)){
for(int i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]);
return 0;
}
int l=0,r=n;
while(l<r){
int mid=(l+r+1)>>1;
for(int i=1;i<=n-mid;++i) test[i]=a[i+mid];
for(int i=1;i<=mid;++i) test[i+n-mid]=a[mid-i+1];
if(calc(n,test)>p) l=mid;
else r=mid-1;
}
for(int i=1;i<=n-l;++i) nw[i]=a[i+l];
for(int i=1;i<=l;++i) nw[i+n-l]=a[l-i+1];
l=0,r=n-r-1;
while(l<r){
int mid=(l+r+1)>>1;
copy(nw+1,nw+n+1,a+1);
for(int i=1;i<=mid;++i) swap(a[i],a[i+1]);
if(calc(n,test)>=p) l=mid;
else r=mid-1;
}
copy(nw+1,nw+n+1,a+1);
for(int i=1;i<=l;++i) swap(a[i],a[i+1]);
for(int i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]);
return 0;
}

简单计数

有一个 \(n\) 个点 \(m\) 条边的有向图,每条边有一个 \(1\) 到 \(m\) 的颜色。

对于每条欧拉回路,定义和谐值为相邻的同色边(一条欧拉回路共有 \(m\) 对 相邻边)对数,问每条欧拉回路的和谐值之和,模 \(998244353\) 输出。

两条欧拉回路不同当且仅当存在边对 \((e_1,e_2)\),在一条欧拉回路中 \(e_2\) 紧随 \(e_1\), 在另一条中 \(e_2\) 不紧随 \(e_1\)。

保证给定的图每个点出度至少为 \(1\),没有自环,对于两个点 \(a\) 和 \(b\),不存在两条 \(a\) 到 \(b\) 的边,且存在欧拉回路。

  • Subtask 1,10pts,\(2\leq n\leq 4\)。

  • Subtask 2,10pts,\(2\leq n\leq 10\) 且 \(m\leq 16\)。

  • Subtask 3,10pts,\(2\leq n\leq 10\)。

  • Subtask 4,20pts,\(2\leq n\leq 23\)。

  • Subtask 5,20pts,\(2\leq n\leq 40\)。

  • Subtask 6,10pts,\(2\leq n\leq 100\)。

  • Subtask 7,20pts,\(2\leq n\leq 300\)。

题解

BEST theorem

一个有向弱连通图如果存在欧拉回路,那么设第 \(i\) 个点的度为 \(deg_i\)(因为存在欧拉回路,入度和出度相等),那么欧拉回路的个数为

\[T(s)\prod_i(deg_i-1)!
\]

其中 \(T(s)\) 为以 \(s\) 为根的外向(内向)生成树个数,\(s\) 为任意一个点。

可以证明图中若存在欧拉回路则以任意一个点为根的内向、外向生成树个数都相等。

为了保持题解的完整性,这里给出BEST theorem的大致证明。

我们选择 \(s\) 点出发的任意一条边作为欧拉回路的起始边,考虑这个欧拉回路除 \(s\) 号点外每个点的最后一条出边,如果这些出边不构成一棵朝向 \(s\) 点的内向生成树的话,那么就形成了一些有向环,那么这个欧拉回路就会在那里绕一圈然后出不去,这显然不可能。

我们考虑证如果我们先决定了一棵生成树,再对于每个点决定了剩下的出边的排列顺序,那么这一定是一条合法的欧拉回路。如果它不是合法的话,那么我们肯定会卡在某个点用完了出边。

如果这个点不是 \(s\) 号点的话,那么进入这个点的边数比出这个点的边数多,这显然不可能。

如果卡在 \(s\) 号点的话,假设有一条边还没有用过,那么这个点的最后一条出边也没有用过,那么这条边指向的点的最后一条出边也没有用过,以此类推。最后我们可以推到这条边指向的点为 \(s\) 号点,那么 \(s\) 也有出边没有用过,所以没有卡住。

所以一条欧拉回路与一棵朝向 \(s\) 的生成树和每个点不在树中的出边任意的一个排列一一对应,所以个数即为

\[T(s)deg_s!\prod_{i\neq s}(deg_i-1)!
\]

由于欧拉回路本质上是个环,一个环会被从起点出发的每条边算上一次,所以方案数要除以 \(deg_s\)。所以个数为

\[T(s)\prod_i(deg_i-1)!
\]

这个定理对于有自环和重边的图同样好用。

可以发现,这个定理的成立与生成树的计数无关,所以有:

It is a property of Eulerian graphs that tv(G) = tw(G) for every two vertices v and w in a connected Eulerian graph G.

以点 \(s\) 为根的外向生成树个数是一个经典问题。

对于有向图的边 \((u,v)\),M[u][v]--,M[v][v]++,然后把矩阵 \(M\) 去掉第 \(s\) 行第 \(s\) 列求行列式,行列式的值即为朝向点a的有向生成树个数。

套路运用

回到原问题,我们考虑枚举每对相邻的同色边 \(u\rightarrow v\rightarrow w\),至多有 \(O(n^3)\) 对这样的边,计数它们在欧拉回路中相邻的方案数。

事实上这个方案数十分容易计算,我们在图中删去 \(u\rightarrow v,v\rightarrow w\),添上 \(u\rightarrow w\) 的边,那么它们在欧拉回路中相邻的方案显然与这个新图中的欧拉回路一一对应。

需要注意的是如果 \(deg_v=1\),新图中 \(v\) 没有连边,这时候我们可以发现 \(u\rightarrow v,v\rightarrow w\) 在欧拉回路中必须相邻,那么只要将原图的欧拉回路个数计入答案即可。

以下假设 \(deg_v>1\),那么我们考虑取 \(s\) 点为 \(v\) 点,删掉了 \(u\rightarrow v,v\rightarrow w\),加上了 \(u\rightarrow w\),那么只有 \(v\) 的度减了 \(1\),所以可以 \(O(1)\) 算右边的连乘式。

那么我们就是要计数以 \(v\) 点为根的外向生成树个数。直接暴力求行列式是 \(O(n^6)\) 的。

至此的解题思路都是套路,已经能得50分了。毕竟时限有6秒。

设原图对应的矩阵为 \(M\),那么我们删掉了 \(u\rightarrow v,v\rightarrow w\),加上了 \(u\rightarrow w\),即 M[u][v]++,M[v][v]--,M[v][w]++,M[w][w]--,M[u][w]--,M[w][w]++

M[w][w]加一减一抵消之后实际效果就是M[u][v]++,M[v][v]--,M[v][w]++,M[u][w]--

因为去掉了第 \(v\) 行第 \(v\) 列,那么实际对于行列式的影响只有 M[u][w]--

众所周知行列式可以如下计算:

\[\left|\begin{matrix}
a & b & c\\
d & e-1 & f\\
g & h & i
\end{matrix}\right|
=\left|\begin{matrix}
a & b & c\\
d & e & f\\
g & h & i
\end{matrix}\right|
+\left|\begin{matrix}
a & b & c\\
0 & -1 & 0\\
g & h & i
\end{matrix}\right|
\]

由矩阵树定理,前面那个行列式对于任意 \(n-1\) 阶余子式都是相等的。所以我们需要找到一种快速计算后面那个行列式的办法。

线代小课堂

子式是选出某些行和列,余子式是删去某些行和列。

代数余子式是余子式乘上 \(-1\) 的行列编号和次方。

详细写一下过程:

\[\left|\begin{matrix}
a & b & c\\
u & v & w\\
x & y & z
\end{matrix}\right|
=|a|(-1)^{1+1}\left|\begin{matrix}
v & w\\
y & z
\end{matrix}\right|
+|b|(-1)^{1+2}\left|\begin{matrix}
u & w\\
x & z
\end{matrix}\right|
+|c|(-1)^{1+3}\left|\begin{matrix}
u & v\\
x & y
\end{matrix}\right|\\
=a\left|\begin{matrix}
v & w\\
y & z
\end{matrix}\right|
-b\left|\begin{matrix}
u & w\\
x & z
\end{matrix}\right|
+c\left|\begin{matrix}
u & v\\
x & y
\end{matrix}\right|
\]

有个这个定理,那么

\[\left|\begin{matrix}
a & b & c\\
0 & -1 & 0\\
g & h & i
\end{matrix}\right|
=0(-1)^{2+1}\left|\begin{matrix}
b & c\\
h & i
\end{matrix}\right|
+(-1)(-1)^{2+2}\left|\begin{matrix}
a & c\\
g & i
\end{matrix}\right|
+0(-1)^{2+3}\left|\begin{matrix}
a & b\\
g & h
\end{matrix}\right|\\
=-\left|\begin{matrix}
a & c\\
g & i
\end{matrix}\right|
\]

注意到只有选到 \(-1\) 的那一行一列时,后面的式子才有值。并且因为选出的行列编号相等,所以符号是 \(1\)。

因此我们的任务就是快速地算出余子式。

那么如何求代数余子式呢?

若 \(\det A\neq 0\),则 \(A\) 具有逆矩阵,且 \(A\) 的逆矩阵就是 \(\frac{1}{\det A}\text{adj}~A\)。

所以我们发现对于一个行列式非 \(0\) 的矩阵 \(A\),求出逆矩阵 \(A^{-1}\) 之后,第 \(i\) 行第 \(j\) 列的代数余子式就是 \(A^{-1}_{j,i}\times \det A\)。

所以M[u][w]减一实际上的效果就是减去了 \(M^{-1}_{w,u}\times \det M\)。这样就可以得到一个\(O(n^4)\) 的做法。

已经可以拿到80分了,考场上推到这一步就该开始打代码了。

CO int N=300+10;
int e[N][N],deg[N];
int fac[N],pre[N],suf[N];
int a[N][N],b[N][N],ib[N][N]; int deter(int n,int a[N][N]){
int ans=1;
for(int i=1;i<=n;++i){
int p=i;
for(int j=i;j<=n;++j)
if(a[j][i]) {p=j;break;}
if(p!=i) swap(a[p],a[i]),ans=mod-ans;
ans=mul(ans,a[i][i]);
int inv=fpow(a[i][i],mod-2);
for(int j=i+1;j<=n;++j){
int coef=mul(mod-a[j][i],inv);
for(int k=i;k<=n;++k) a[j][k]=add(a[j][k],mul(coef,a[i][k]));
}
}
return ans;
}
void inver(int n,int a[N][N],int b[N][N]){
for(int i=1;i<=n;++i) fill(b[i]+1,b[i]+n+1,0),b[i][i]=1;
for(int i=1;i<=n;++i){
int p=i;
for(int j=i;j<=n;++j)
if(a[j][i]) {p=j;break;}
if(p!=i) swap(a[p],a[i]),swap(b[p],b[i]);
int inv=fpow(a[i][i],mod-2);
for(int j=i;j<=n;++j) a[i][j]=mul(a[i][j],inv);
for(int j=1;j<=n;++j) b[i][j]=mul(b[i][j],inv);
for(int j=1;j<=n;++j)if(j!=i){
int coef=mod-a[j][i];
for(int k=i;k<=n;++k) a[j][k]=add(a[j][k],mul(coef,a[i][k]));
for(int k=1;k<=n;++k) b[j][k]=add(b[j][k],mul(coef,b[i][k]));
}
}
}
int main(){
// freopen("count.in","r",stdin),freopen("count.out","w",stdout);
int n=read<int>();
for(int m=read<int>();m--;){
int c=read<int>(),u=read<int>(),v=read<int>();
e[u][v]=c,++deg[v];
a[u][v]=add(a[u][v],mod-1),++a[v][v];
} fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
pre[0]=1;
for(int i=1;i<=n;++i) pre[i]=mul(pre[i-1],fac[deg[i]-1]);
suf[n+1]=1;
for(int i=n;i>=1;--i) suf[i]=mul(suf[i+1],fac[deg[i]-1]);
for(int i=1;i<n;++i) copy(a[i]+1,a[i]+n,b[i]+1);
int det=deter(n-1,b),all=mul(det,pre[n]); int ans=0;
for(int v=1;v<=n;++v){
if(deg[v]==1){
for(int u=1;u<=n;++u)if(e[u][v])
for(int w=1;w<=n;++w)if(e[v][w]==e[u][v])
ans=add(ans,all);
continue;
}
int rhs=mul(pre[v-1],mul(fac[deg[v]-2],suf[v+1]));
for(int i=1;i<=n;++i)if(i!=v)
for(int j=1;j<=n;++j)if(j!=v)
b[i-(i>v)][j-(j>v)]=a[i][j];
inver(n-1,b,ib);
for(int u=1;u<=n;++u)if(e[u][v])
for(int w=1;w<=n;++w)if(e[v][w]==e[u][v]){
int sum=add(det,mod-mul(det,ib[w-(w>v)][u-(u>v)])); // determinant all equal
ans=add(ans,mul(sum,rhs));
}
}
printf("%d\n",ans);
return 0;
}

这个做法瓶颈在于对于每个 \(v\),在去掉第 \(v\) 行第 \(v\) 列之后求逆矩阵。

简单优化

考虑高斯消元求逆矩阵的过程,如果我们消去了除了 \(v\) 以外所有的元,提取出逆矩阵去掉第 \(v\) 行第 \(v\) 列之后的部分,那么效果是一样的。

基于这点我们可以分治消元,例如solve(l,r)表示当前还没有消去 l~r 的元,对于mid+1~r这些行的所有自由元,将其消去后调用solve(l,mid),对于l~mid这些行的所有自由元,消去后调用solve(mid+1,r)

时间复杂度 \(O(n^3\log n)\)。由于常数不是很大,这个做法可以通过。

但是有个问题,例如消mid+1~r的元的时候,你是不能把l~mid的行跟它们交换的。

所以这就不一定消得出来,出题人刻意卡是能卡掉的。

我在读入的时候加了个随机化,然后就可以过了。

CO int N=300+10;

struct matrix {int n,v[N][N];};

int deter(matrix a){
int ans=1;
for(int i=1;i<=a.n;++i){
int p=i;
for(int j=i;j<=a.n;++j)
if(a.v[j][i]) {p=j;break;}
if(p!=i) swap(a.v[p],a.v[i]),ans=mod-ans;
ans=mul(ans,a.v[i][i]);
int inv=fpow(a.v[i][i],mod-2);
for(int j=i+1;j<=a.n;++j){
int coef=mul(mod-a.v[j][i],inv);
for(int k=i;k<=a.n;++k)
a.v[j][k]=add(a.v[j][k],mul(coef,a.v[i][k]));
}
}
return ans;
} int n;
int e[N][N],deg[N];
int fac[N],pre[N],suf[N];
int det,ans; void solve(int l,int r,CO matrix&a,CO matrix&b){
if(l==r){
if(deg[l]==1) return;
int rhs=mul(pre[l-1],mul(fac[deg[l]-2],suf[l+1]));
for(int u=1;u<=n;++u)if(e[u][l])
for(int w=1;w<=n;++w)if(e[l][w]==e[u][l]){
int sum=add(det,mod-mul(det,b.v[w][u]));
ans=add(ans,mul(sum,rhs));
}
return;
}
int mid=(l+r)>>1;
matrix la=a,lb=b;
for(int i=mid+1;i<=r;++i){
int p=i;
for(int j=i;j<=r;++j)
if(la.v[j][i]) {p=j;break;}
if(p!=i) swap(la.v[p],la.v[i]),swap(lb.v[p],lb.v[i]);
assert(la.v[i][i]);
int inv=fpow(la.v[i][i],mod-2);
for(int j=l;j<=r;++j) la.v[i][j]=mul(la.v[i][j],inv);
for(int j=1;j<=n;++j) lb.v[i][j]=mul(lb.v[i][j],inv);
for(int j=1;j<=n;++j)if(j!=i){
int coef=mod-la.v[j][i];
for(int k=l;k<=r;++k)
la.v[j][k]=add(la.v[j][k],mul(coef,la.v[i][k]));
for(int k=1;k<=n;++k)
lb.v[j][k]=add(lb.v[j][k],mul(coef,lb.v[i][k]));
}
}
solve(l,mid,la,lb);
matrix ra=a,rb=b;
for(int i=l;i<=mid;++i){
int p=i;
for(int j=i;j<=mid;++j)
if(ra.v[j][i]) {p=j;break;}
if(p!=i) swap(ra.v[p],ra.v[i]),swap(rb.v[p],rb.v[i]);
assert(ra.v[i][i]);
int inv=fpow(ra.v[i][i],mod-2);
for(int j=l;j<=r;++j) ra.v[i][j]=mul(ra.v[i][j],inv);
for(int j=1;j<=n;++j) rb.v[i][j]=mul(rb.v[i][j],inv);
for(int j=1;j<=n;++j)if(j!=i){
int coef=mod-ra.v[j][i];
for(int k=l;k<=r;++k)
ra.v[j][k]=add(ra.v[j][k],mul(coef,ra.v[i][k]));
for(int k=1;k<=n;++k)
rb.v[j][k]=add(rb.v[j][k],mul(coef,rb.v[i][k]));
}
}
solve(mid+1,r,ra,rb);
} int per[N]; int main(){
freopen("count.in","r",stdin),freopen("count.out","w",stdout);
srand(20030506);
read(n);
for(int i=1;i<=n;++i) per[i]=i;
random_shuffle(per+1,per+n+1); // edit 1
matrix a=(matrix){n};
for(int i=1;i<=n;++i) fill(a.v[i]+1,a.v[i]+n+1,0);
for(int m=read<int>();m--;){
int c=read<int>(),u=per[read<int>()],v=per[read<int>()];
e[u][v]=c,++deg[v];
a.v[u][v]=add(a.v[u][v],mod-1),++a.v[v][v];
} fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
pre[0]=1;
for(int i=1;i<=n;++i) pre[i]=mul(pre[i-1],fac[deg[i]-1]);
suf[n+1]=1;
for(int i=n;i>=1;--i) suf[i]=mul(suf[i+1],fac[deg[i]-1]); a.n=n-1,det=deter(a);
int all=mul(det,pre[n]);
for(int v=1;v<=n;++v)if(deg[v]==1)
for(int u=1;u<=n;++u)if(e[u][v])
for(int w=1;w<=n;++w)if(e[v][w]==e[u][v])
ans=add(ans,all); a.n=n;
matrix b=(matrix){n};
for(int i=1;i<=n;++i) fill(b.v[i]+1,b.v[i]+n+1,0),b.v[i][i]=1;
solve(1,n,a,b);
printf("%d\n",ans);
return 0;
}

test20191210 钟子谦的更多相关文章

  1. 简单数据结构题(from 钟子谦——IOI2018集训队自选题)

    简单数据结构题(from 钟子谦--IOI2018集训队自选题) 试题描述 给一棵 \(n\) 个点的树,点权开始为 \(0\) ,有 \(q\) 次操作,每次操作是选择一个点,把周围一圈点点权 \( ...

  2. OTZ%%%子谦。大佬

    又上了节课...俩题 计算系数    组合数问题... 要不是大佬指点就只能阶乘暴力算了 (主要还是我忘了杨辉三角) 杨辉三角与组合数C有着千丝万缕的联系,在计算,使用方面相当方便. 先说计算系数 计 ...

  3. 【洛谷P1313 计算系数】

    题目连接 #include<algorithm> #include<iostream> #include<cstring> #include<cstdio&g ...

  4. 【洛谷P2822 组合数问题】

    题目连接 #include<iostream> #include<cstring> #include<cstdio> #include<cctype> ...

  5. ZJOI2019Round#2

    乱听课记录 关于树的分治问题&杂题选讲 张哲宇 边分治 (边分不是很鸡肋吗) 例题一 题目大意:给出两颗有正负边权的树,求出两个点\(u,v​\)使得两棵树中\((u,v)​\)距离的和最大. ...

  6. noip2018——题解&总结

    近期正在疯狂复习某些东西,这篇博客尽量年底更完……(Day2T2除外) 好了,所有的希望都破灭了,原来这就是出题人的素质.——一个被欺骗的可怜 $OIer$ 人生中倒数第三次 $noip$ (Mayb ...

  7. 海亮NOIP集训-每日总结

    [总结] xzh 2021暑假每日结 2021年7月12日 内容主题 DP,树型DP(讲解人:王修涵) 考场题目总结 T1: 考场简单想法: 算出两两点间距离,贪心,所用时间 \(1.5h\) 左右. ...

  8. 【转载】一封面向社会,关于对近日来 CCF 不当行为之抗议的公开信

    原文链接:https://101001011.github.io/2022/06/11/zhi-ccf-de-yi-feng-gong-kai-xin/ 原文作者:CCA(CCA's Blog) 前天 ...

  9. 【五】将博客从jekyll迁移到了hexo

    本系列有五篇:分别是  [一]Ubuntu14.04+Jekyll+Github Pages搭建静态博客:主要是安装方面  [二]jekyll 的使用 :主要是jekyll的配置  [三]Markdo ...

随机推荐

  1. 【发现】visualvm是jdk自带的一款监控工具

    visualvm是jdk自带的一款监控工具.它提供了一个可视界面,用于查看 Java 虚拟机上运行的基于 Java 技术的程序的详细信息.VisualVM 对 Java Development Kit ...

  2. vux scroller在iOS13上,一停止滑动就跳到顶部

    转载:https://blog.csdn.net/sllailcp/article/details/102502452 今天客户反馈的问题,说在最新版的iOS上(iOS13),滑动列表,滑完就会跳到顶 ...

  3. 在 flutter 上使用 c 代码 - (一) 有源码的项目

    在 flutter 的 1.10.x 后的分支, dart:ffi 被并入 flutter, 现在 flutter 中也可以使用 ffi 了. 这东西是啥玩意呢, 就是让 dart 可以直接调用 c/ ...

  4. FusionInsight大数据开发---Streaming应用开发

    Streaming应用开发 掌握Streaming基本业务开发流 熟悉Streaming常用API接口使用 掌握Streaming业务设计基本原则 了解Streaming应用开发环境 了解CQL开发流 ...

  5. k8s-Node(节点)

    k8s-Node(节点) Node(节点)是k8s集群中相对于Master而言的工作主机.Node可以是一台物理主机,也可以是一台虚拟机(VM).在每个Node上运行用于启动和管理Pid的服务Kube ...

  6. Linux学习笔记之vim操作指令大全

    0x00 关于Vim Vim是款强大的文本编辑器,但是众多指令需要学习,这次记录了指令大全方便以后翻阅. Vim的几种模式 正常模式:可以使用快捷键命令,或按:输入命令行. 插入模式:可以输入文本,在 ...

  7. springboot 全局异常捕获,异常流处理业务逻辑

    前言 上一篇文章说到,参数校验,往往需要和全局的异常拦截器来配套使用,使得返回的数据结构永远是保持一致的.参数异常springboot默认的返回结构: { "timestamp": ...

  8. RabbitMQ的简单模式快速入门与超时异常的处理方法

    本文适合JAVA新人,想了解RabbitMQ又不想去看官网文档的人(英语水看的头疼(◎﹏◎),但建议有能力还是去看官网文档). 消息队列MQ(一) MQ全称为Message Queue,消息队列是应用 ...

  9. HTML不换行,多余用省略号代替

    最主要的css样式: white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 例子: <!DOCTYPE html> ...

  10. $parsers & $formatters

    一.理解 $parsers 和 $formatters Angular 是MVVM框架,model层数据变化了会通知view层更新,同样的view层更新了也会通知到model $parsers 和 $ ...