把过去和现在的笔记记录也搬了过来,也算是给以后留个念想吧,想想一开始打acm就是图一乐,后来发现这游戏还挺上头的,也算是度过了一段电竞生涯(xs)
早些时候的笔记写的好中二,连我自己看着都羞耻。
不过,就喜欢这种羞耻的感觉。
收录的题目大部分是个人认为质量不错的题目,以DP为主,非DP的题目都用※进行了标识。
当然,有些题解思路本身也是源自其他人的,不过除非特殊标注,否则都是用的自己的代码。
题目大意:
1657E Star MST
题目大意: 给一个点数位n(范围为250)的完全图,每条边边权在k(范围为250)以内,要求最小生成树权值等于从1到其它边的边权数之和的图数量,模998244353。
解:
是经典的k的n次方分配数量问题,限制条件可以转化为对于每个点,1到它的距离大于等于它到所有其它点的距离(否则的话最小生成树就可以连更短的边),因此实际上其它的边我完全不需要记录,我只要一个数组来记录从1到每一个点的距离,以此就能够算出这种情况下图的数量。
N在250以内的数据决定了无法状压,那么怎么办呢?
这里出现了一个在这种问题中非常常用的“解冻法”,假设一共有n个点,一开始从i向n-1遍历,假设最初所有边的权值都为1,每次i的增加过程中我都会解冻一些点,被解冻的点当i+1的时候本身也会+1,冻住的点依然为1。显然在我解冻的时候解冻的那些边权值都还是为1的,那么和他们相连的边的值可以取1到i的任意值,并且由于提前解冻的边已经经过不断次+1,他们的权值肯定会大于新解冻的边,所以实现了无后效性。
这种状态转移的方式巧妙地无视了原本数据的顺序性,可能不是唯一的解法,但是非常无脑。
用dp[i][j]表示遍历到i且有j个点还在冻结时的方案数,最后要求的就是dp[n][0](为什么是dp[n][0]而不是∑dp[n][i],不告诉你),起始时,dp[0][n]=1,状态转移的时候从dp[i-1][j]通过遍历j以及这次要解冻的数量k来实现,转移方程是dp[i][j-k]+=dp[i-1][j]*C(n-j,k)*pow(i,边数),边数的算法通过等差数列实现,复杂度为O(n3)。
硬去想解冻法的实现原理还是很绕的,不过还是建议去把它彻底理解出来,实际上是一种非常实用的算法,或许可以尝试一下把它用在之前的Arena上呢……
代码
代码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
| #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <algorithm> #include <utility> #include <vector> #include <istream> #include <map> #include <cmath> #include <stack> #include <set> #include <cstring> #include <string> #define ll long long #define maxn 1000005 #define mdl 998244353 #define clr(a,n) for(int i=0;i<n;i++)a[i]=0 #define cfast std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define pll pair<ll,ll> #define inc(i,a,n) for(int i=a;i<n;i++) using namespace std; ll dp[255][255], hzh[255]; ll cp[505][70005]; ll C[255][255]; void initC(const int& n) { for (int i = 0; i <= n; ++i) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; ++j) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mdl; } } ll cpow(ll x, ll n) { int tmp = n; if (cp[x][n] != 0)return cp[x][n]; ll ans = 1; if (n == 0)ans = 1; else if (n % 2 == 1) { ans = x * cpow(x, n - 1) % mdl; } else { ll a = cpow(x, n / 2); ans = a * a % mdl; } cp[x][n] = ans; return ans; } inline int tblock(int x, int y) { return x* (x - 1) / 2 - (x - y) * (x - y - 1) / 2; } int main() { cfast; initC(254); int n, k; cin >> n >> k; dp[0][n - 1] = 1; for (int i = 1; i <= k; i++) { inc(a, 0, n) { for (int b = 0; b <= a; b++) { dp[i][a - b] += (dp[i - 1][a] * C[a][b]) % mdl * cpow(i, tblock(a, b)) % mdl; dp[i][a - b] = dp[i][a - b] % mdl; } } } cout << dp[k][0]; }
|
Copyright Notice: 此文章版权归EmiteInna所有,多看看吧。