A
“尽可能多的边”、“边权最大”、“不存在环” = “最大生成树”。于是就想到了跑 k 遍 kruskal,愉快的 O(mlogm + k(m + n))) 然后愉快的超时(最大数据要跑将近40秒)。不过还是有50分的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAXN 1010
#define MAXM 300300
int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + (c - '0');
c = getchar();
}
return x ;
}
int n = 0; int m = 0;
int k = 0;
struct Tedge{
int x, y;
int val; int idx;
int times;
bool used;
bool operator < (const Tedge& b)const {
return val < b.val;
}
bool operator > (const Tedge& b)const {
return val > b.val;
}
}edge[MAXM];
bool comp(Tedge a, Tedge b){
return a.val > b.val;
}
int parent[MAXN] = { 0 };
void init(){
for(int k = 1; k <= n; k++){
parent[k] = k;
}
}
int findRoot(int x){
if(x == parent[x]){
return x;
}
return parent[x] = findRoot(parent[x]);
}
void unionVertices(int x, int y){
parent[findRoot(x)] = findRoot(y);
}
int main(){
n = read(); m = read(); k = read();
for(int i = 1; i <= m; i++){
edge[i].x = read(); edge[i].y = read();
edge[i].val = read(); edge[i].idx = i;
}
sort(edge+1, edge+m+1, comp);
for(int i = 1; i <= k; i++){
init();
for(int j = 1; j <= m; j++){
Tedge e = edge[j];
if(!e.used and findRoot(e.x) != findRoot(e.y)){
unionVertices(e.x, e.y);
edge[j].used = 1;
edge[e.idx].times = i;
}
}
}
for(int i = 1; i <= m; i++){
printf("%d\n", edge[i].times);
}
return 0;
}
(以上是50分做法)
我们考虑维护 k 个并查集,来表示在时间 t 时整个图的连通性。对于第 t 个并查集来说,它存储的时间 t 时的最大生成树(删除的边)。
在主函数中,枚举每一条边(从大到小),找到这条边所连的两个点在在并查集中不连通的第一个并查集(假设这个并查集是第 t 个),标记这个边被删除(就是被选中作为第 t 次的最大生成树中的一条边)的时间 t 。然后将这两个点在这个并查集里连起来(边被选中了),再然后我们做一个这条边的时间标记,下一次查询到相同节点的边我们就从这个标记开始往后搜。
AC 代码:
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1010
#define MAXM 300300
#define MAXK 10010
inline int read(){
int p = 0; int f = 1;
char c = getchar();
while(c > '9' or c<'0'){
if(c == '-') f =- 1;
c = getchar();
}
while(c >= '0' and c <= '9'){
p = p * 10 + c - '0';
c=getchar();
}
return p * f;
}
int n = 0; int m = 0;
int k = 0;
struct Tedge{
int x, y;
int val; int idx;
}edge[MAXM];
bool comp(Tedge a, Tedge b){
return a.val > b.val;
}
int parent[MAXN][MAXK] = { 0 };
void init(){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= k; j++){
parent[i][j] = i;
}
}
}
int findRoot(int x, int kth){
if(x == parent[x][kth]){
return x;
}
return parent[x][kth] = findRoot(parent[x][kth], kth);
}
int vis[MAXM] = { 0 };
int cur[MAXN][MAXN] = { 0 };
int main(){
n = in; m = in; k = in;
init();
for(int i = 1; i <= m ;i++){
edge[i].x = in; edge[i].y = in;
edge[i].val = in; edge[i].idx = i;
}
sort(edge+1, edge+m+1, comp);
for(int i = 1; i <= m; i++){
int x = edge[i].x; int y = edge[i].y;
int t = cur[x][y] + 1;
int xRoot = findRoot(x, t); int yRoot = findRoot(y, t);
while(xRoot == yRoot and t < k){
t++; xRoot = findRoot(x, t); yRoot = findRoot(y, t);
}
if(xRoot == yRoot) continue;
vis[edge[i].idx] = t;
parent[yRoot][t] = xRoot;
cur[x][y] = t; cur[y][x] = t;
}
for(int i = 1; i <= m; i++){
printf("%d\n", vis[i]);
}
return 0;
}
B
首先,只有全白的情况是不可能涂黑的,所以特判全白,输出-1。然后我们在分析过程中会发现以下事实:
- 记 c 表示至少有一个白格的列的个数,我们发现,在矩阵中的某一行完全变黑之前,c 是不会减少的。当矩阵中的某一行完全变黑之后,每一次操作可以将 c 减少 1。
- 如果在第 r 列有一个黑色方格,那我们可以通过 x 次操作将第 r 行全部变成黑色。其中我们记第 r 行中初始时黑色的个数为 a,则 x = n - a。(
x 其实就是这一行白格子的个数qwq) - 如果在第 r 列一个黑色方格都没有,则我们需要一次额外的操作在第 r 列里搞一个黑格出来,所以需要的操作次数为 x - 1。
综上所述,总的操作次数为:
c
+
min
i
=
1
n
x
o
r
(
x
−
1
)
c + \min_{i=1}^n x \; or \; (x-1)
c+i=1minnxor(x−1)
#include<bits/stdc++.h>
using namespace std;
#define MAXN 505
int n = 0;
int mmap[MAXN][MAXN] = { 0 };
int cnt = 0;
int c = 0;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("\n");
for(int j = 1; j <= n; j++){
char temp;
scanf("%c", &temp);
if(temp == '#'){
cnt++;
mmap[i][j] = 1;
}
}
}
if(cnt == 0){
printf("%d\n", -1);
return 0;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(mmap[j][i] == 0){
c++; break;
}
}
}
int ans = 1 << 30;
for(int i = 1; i <= n; i++){
int x = 0; bool is = false;
for(int j = 1; j <= n; j++){
if(mmap[j][i]) is = true;
if(!mmap[i][j]){
x++;
}
}
if(!is) x++;
ans = min(x, ans);
}
printf("%d\n", ans + c);
return 0;
}
C
我们设一个长度为 2m 的 n 的因数的序列 A:
a
1
,
a
2
,
⋯
,
a
2
m
a_1, a_2, \cdots , a_{2m}
a1,a2,⋯,a2m。如果他们的乘积 x 小于
n
m
n^m
nm 也就是:
x
=
a
1
×
a
2
×
⋯
×
a
2
m
=
∏
i
=
1
2
m
a
i
<
n
m
x = a_1 \times a_2 \times \cdots \times a_{2m} = \prod_{i=1}^{2m}a_i < n^m
x=a1×a2×⋯×a2m=i=1∏2mai<nm
则另一个长度为 2m 的 n 的因数的序列为 A’:
n
a
1
,
n
a
2
,
⋯
,
n
a
2
m
\frac{n}{a_1}, \frac{n}{a_2}, \cdots, \frac{n}{a_{2m}}
a1n,a2n,⋯,a2mn。他们的乘积 x’ 就是:
x
′
=
n
a
1
×
n
a
2
×
⋯
×
n
a
2
m
=
n
2
m
∏
i
=
1
2
m
a
i
=
n
2
m
x
x' = \frac{n}{a_1} \times \frac{n}{a_2} \times \cdots \times \frac{n}{a_{2m}} = \frac{n^{2m}}{\prod_{i=1}^{2m}a_i} = \frac{n^{2m}}{x}
x′=a1n×a2n×⋯×a2mn=∏i=12main2m=xn2m
因为 x <
n
m
n^{m}
nm 所以 x’ >
n
m
n^m
nm。
由此我们知道一个乘积小于
n
m
n^m
nm 的序列和乘积大于
n
m
n^m
nm 的序列是一一对应的。即这两种类型的子串数量一样。所以我们只需要计算乘积等于
n
m
n^m
nm 的序列的数量,我们记为 x。然后计算所有可能的排列的数量,我们记为y。我们要的答案就是:
a
n
s
=
y
−
x
2
ans = \frac{y-x}{2}
ans=2y−x
附上 std 代码:
#include <bits/stdc++.h>
using namespace std;
const int P = 998244353;
int power(int x, int y) {
int r = 1;
while (y) {
if (y & 1) {
r = (long long) r * x % P;
}
x = (long long) x * x % P;
y >>= 1;
}
return r;
}
int main() {
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
auto solve = [&](int e) {
vector<int> dp(e * m + 1);
dp[0] = 1;
for (int i = 1; i <= m * 2; ++i) {
for (int j = 1; j <= e * m; ++j) {
dp[j] = (dp[j] + dp[j - 1]) % P;
}
for (int j = e * m; j > e; --j) {
dp[j] = (dp[j] + P - dp[j - e - 1]) % P;
}
}
return dp[e * m];
};
int same = 1;
int ways = 1;
for (int p = 2; p * p <= n; ++p) {
if (n % p == 0) {
int e = 0;
while (n % p == 0) {
n /= p;
++e;
}
same = (long long) same * solve(e) % P;
ways *= e + 1;
}
}
if (n != 1) {
same = (long long) same * solve(1) % P;
ways *= 2;
}
cout << (long long) (same + power(ways, m * 2)) * (P + 1) / 2 % P << "\n";
return 0;
}