倒着慢慢总结一下吧……
A
一个非常明显的最小割模型,但是需要让一个点向一个矩阵里面的所有点连边,直接连是一定会爆空间/时间的。
所以需要把这个矩阵倍增划分,比较像做RMQ的ST表或者二维线段树。
比如一个2x2的矩阵就要划分为这个样子:
这样划分的话可以把一个点向一个矩阵里面的所有点连边转化为只向至多4个点连边,就可以解决这个题目了。
点数是O(n^2log^2n+Q)级别..有点多,不过dinic还是跑的挺快的
B
后缀自动机学习理解中……
C
题解的一句话提示性很明显,6和12是没有区别的,17,19,23和1也是没有区别的,没有区别的数字之间可以化为一个等价类,这样一共就只有14种等价类,状态最多只有14x1728000种,就可以状压DP了,30s绰绰有余。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
#define N 20
typedef long long LL;
using namespace std;
int n , m , M;
struct edge
{
int x , next;
}e[N * N];
int f[1728000][14];
int dir[] = {1 , 2 , 3 , 5 , 6 , 7 , 10 , 11 , 13 , 14 , 15 , 21 , 22 , 26 , 1 << 30};
int bel[] = {0 , 1 , 2 , 3 , 2 , 5 , 6 , 7 , 2 , 3 , 10 , 11 , 6 , 13 , 14 , 15 , 2 , 1 , 6 , 1 , 10 , 21 , 22 , 1 , 6 , 5 , 26 , 3 , 14};
int cnt[N] , ha[N] , power[N] , pre[N] , mcnt;
void init()
{
int i , j;
scanf("%d%d",&n,&m);
memset(cnt , 0 , sizeof(cnt));
memset(ha , -1 , sizeof(ha));
M = 0;
for (i = 0 ; dir[i] <= n ; ++ i)
ha[dir[i]] = i , ++ M;
for (i = 1 ; i <= n ; ++ i)
++ cnt[ha[bel[i]]];
power[0] = 1;
for (i = 0 ; i < M ; ++ i)
power[i + 1] = power[i] * (cnt[i] + 1);
//cout << power[M] << ":";
memset(pre , -1 ,sizeof(pre));
mcnt = 0;
for (i = 0 ; i < M ; ++ i)
for (j = i ; j < M ; ++ j)
if (__gcd(dir[i] , dir[j]) == 1)
{
e[mcnt] = (edge){j , pre[i]} , pre[i] = mcnt ++;
if (i != j)
e[mcnt] = (edge){i , pre[j]} , pre[j] = mcnt ++;
}
}
void work()
{
int i , j , x , y , k;
memset(f , 0 , sizeof(f));
f[0][0] = 1 % m;
for (i = 0 ; i < power[M] ; ++ i)
for (x = 0 ; x < M ; ++ x) if (f[i][x])
{
for (j = pre[x] ; ~j ; j = e[j].next)
{
y = e[j].x;
k = i - i / power[y + 1] * power[y + 1] , k /= power[y];
if (k >= cnt[y]) continue;
f[i + power[y]][y] += f[i][x] * (cnt[y] - k);
f[i + power[y]][y] %= m;
}
}
int ans = 0;
for (i = 0 ; i < M ; ++ i)
{
ans += f[power[M] - 1][i];
if (ans >= m)
ans -= m;
}
cout << ans << endl;
}
int main()
{
int _; cin >> _ ; while (_ --)
init() , work();
return 0;
}
J
这道题也是非常有意思的,也比较有启发性。方法题解说的很明白,只是之前不太理解题解说的“拿树状数组维护”,就照着自己的想法用线段树来维护,结果是TLE……
常数优化无果后,就看了看标程理解一下这个树状数组。
这题用数据结构需要支持:
1、单点修改
2、区间查询最大值
单点修改有个特点,一个位置的值只会增加不会减少,所以区间的最大值不会减少。
区间查询也有个特点,就是查询是一个区间[i,x]的最大值,但查询的时候[x+1,n]都是没有值的(0),就可以等价于查询一个[i,n],一个后缀的最大值。
所以就可以套用树状数组的形式来实现这些操作了,只需把后缀倒过来改为前缀即可,效率比线段树快的多得多,是不是我的线段树常数太搓了……
K
题解的solve1非常炫酷……但是不知道为什么我写的会T,后来换了几个更新的顺序(姿势)就AC了,果然这个对代码的效率也有比较大的影响。