HNOI2015做题笔记
HNOI2015
亚瑟王(概率DP)
根据期望的线性性,我们只需要算出每一种卡牌触发的概率就可以算出期望的值
考虑与第\(i\)张卡牌触发概率相关的量,除了\(p_i\)还有前\(i-1\)张卡牌中触发过的卡牌的数量。
假设前\(i\)张卡牌中触发了\(j\)张的概率为\(f_{i,j}\),那么第\(i\)张卡牌的触发概率就是\(\sum f_{i-1,j} \times (1 - (1 - p_i)^{R - j})\)
一个不好理解的地方:对于某一张卡牌,它触发的概率与之前卡牌在哪一个回合触发无关,只与数量有关系,触发的数量决定可能会触发这一张卡牌的回合数。
所以我们需要求的是\(f_{i,j}\)。不难设计出\(f_{i,j}\)的DP转移:\(f_{i,j} = f_{i-1,j} \times (1 - p_i) ^ {R - j} + f_{i-1,j-1} \times (1 - (1 - p_i) ^ {R - j + 1})\)
#include<iostream>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<cassert>
//This code is written by Itst
using namespace std;
long double dp[221][133] , p[221];
int T , N , R , d[221];
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
for(cin >> T ; T ; --T){
cin >> N >> R;
memset(dp , 0 , sizeof(dp));
for(int i = 1 ; i <= N ; ++i)
cin >> p[i] >> d[i];
dp[0][0] = 1;
for(int i = 1 ; i <= N ; ++i)
for(int j = 0 ; j <= R ; ++j){
dp[i][j] = dp[i - 1][j] * pow(1 - p[i] , R - j) + (j ? (dp[i - 1][j - 1]) * (1 - pow(1 - p[i] , R - j + 1)) : 0);
}
long double sum = 0;
for(int i = 1 ; i <= N ; ++i)
for(int j = 0 ; j < R ; ++j)
sum += d[i] * dp[i - 1][j] * (1 - pow(1 - p[i] , R - j));
cout << fixed << setprecision(10) << sum << endl;
}
return 0;
}
接水果(整体二分、扫描线)
这题最重要的问题是如何抽象描述“盘子的路径是水果的路径的子路径”的信息
不妨设\(L_i = dfn_i , R_i = dfn_i + size_i - 1\)
很难不难想到将一条路径\(i,j\)转化为二维平面上的一个点\((L_i , L_j)\)。
设路径\(u,v(L_u < L_v)\)包含路径\(x,y(L_x < L_y)\),那么:
①\(LCA(x,y) \neq x\),这个时候需要满足\(L_u \in [L_x , R_x] , L_v \in [L_y , R_y]\)
②\(LCA(x,y) = x\),设\(x\)到\(y\)路径上第一个点为\(z\),那么\(u\)和\(v\)一个在\(y\)的子树内,一个不在\(z\)的子树内,即\(L_u \in [1 , L_z - 1] , L_v \in [L_y , R_y]\)或\(L_u \in [L_y , R_y] , L_v \in [R_z + 1 , N]\)
无论如何路径\(x,y\)能够影响的点\((L_u,L_v)\)都会在\(1\)个或\(2\)个矩形内。
如果只有一次询问,可以首先二分一个值\(mid\),将\(val \leq mid\)的所有矩形选中,扫描线看点\((L_u,L_v)\)被覆盖了多少次
如果有多组询问直接整体二分就好
注意一些卡常细节:1、整体二分过程中不存矩形,而存扫描线(矩形边界);2、将矩形边界和询问按照横坐标排序,并且在整体二分分治的过程中保持横坐标有序,那么每一层就可以双指针扫一遍完成扫描线的工作;3、在有倍增的地方改用树剖
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<vector>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
}
#define swap(i , j) ((i) ^= (j) ^= (i) ^= (j))
const int MAXN = 40007;
struct Query{
int l , r , K , ind;
bool operator <(const Query a)const{
return l == a.l ? r < a.r : l < a.l;
}
}que[MAXN] , que1[MAXN] , que2[MAXN];
struct Cng{
int x , l , r , val , pos;
bool operator <(const Cng a)const{
return x < a.x;
}
}cng[MAXN << 2] , cng1[MAXN << 2] , cng2[MAXN << 2];
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , dfn[MAXN] , sz[MAXN] , son[MAXN] , fa[MAXN] , dep[MAXN] , top[MAXN];
int N , M , Q , ts , cntEd , cntL , cntP , lsh[MAXN] , ans[MAXN];
namespace BIT{
#define lowbit(i) (i & -i)
int BIT[MAXN];
inline void modify(int x , int num){
while(x <= N){
BIT[x] += num;
x += lowbit(x);
}
}
inline int query(int x){
int sum = 0;
while(x){
sum += BIT[x];
x -= lowbit(x);
}
return sum;
}
}
using BIT::modify;
using BIT::query;
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
void dfs1(int x , int p){
sz[x] = 1;
fa[x] = p;
dep[x] = dep[p] + 1;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p){
dfs1(Ed[i].end , x);
sz[x] += sz[Ed[i].end];
if(sz[son[x]] < sz[Ed[i].end])
son[x] = Ed[i].end;
}
}
void dfs2(int x , int t){
top[x] = t;
dfn[x] = ++ts;
if(!son[x])
return;
dfs2(son[x] , t);
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != fa[x] && Ed[i].end != son[x])
dfs2(Ed[i].end , Ed[i].end);
}
inline int jump(int x , int y){
int tx = top[x] , ty = top[y] , pst = 0;
while(tx != ty){
pst = tx;
x = fa[tx];
tx = top[x];
}
return x == y ? pst : son[y];
}
inline void add(int x , int l , int r , int val , int pos){
cng[++cntP] = (Cng){x , l , r , val , pos};
}
void solve(int l , int r , int L , int R , int ql , int qr){
if(l > r)
return;
if(ql == qr){
for(int i = l ; i <= r ; ++i)
ans[que[i].ind] = ql;
return;
}
int mid = (ql + qr) >> 1 , p1 = l , p2 = 0 , p3 = 0 , P1 = L , P2 = 0 , P3 = 0;
while(p1 <= r || P1 <= R)
if(P1 <= R && (p1 > r || que[p1].l >= cng[P1].x)){
if(cng[P1].val <= mid){
modify(cng[P1].l , cng[P1].pos);
modify(cng[P1].r + 1 , -cng[P1].pos);
cng1[++P2] = cng[P1];
}
else
cng2[++P3] = cng[P1];
++P1;
}
else{
int sum = query(que[p1].r);
if(sum >= que[p1].K)
que1[++p2] = que[p1];
else{
que2[++p3] = que[p1];
que2[p3].K -= sum;
}
++p1;
}
for(int i = l ; i < l + p2 ; ++i)
que[i] = que1[i - l + 1];
for(int i = l + p2 ; i <= r ; ++i)
que[i] = que2[i - l - p2 + 1];
for(int i = L ; i < L + P2 ; ++i)
cng[i] = cng1[i - L + 1];
for(int i = L + P2 ; i <= R ; ++i)
cng[i] = cng2[i - L - P2 + 1];
solve(l , l + p2 - 1 , L , L + P2 - 1 , ql , mid);
solve(l + p2 , r , L + P2 , R , mid + 1 , qr);
}
#define end(x) (dfn[x] + sz[x] - 1)
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read(); M = read(); Q = read();
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
dfs1(1 , 0);
dfs2(1 , 1);
for(int i = 1 ; i <= M ; ++i){
int l = read() , r = read() , val = read();
lsh[i] = val;
if(dfn[l] > dfn[r])
swap(l , r);
if(dfn[r] >= dfn[l] && dfn[r] < dfn[l] + sz[l]){
int t = jump(r , l);
add(1 , dfn[r] , end(r) , val , 1);
add(dfn[t] , dfn[r] , end(r) , val , -1);
if(end(t) != N){
add(dfn[r] , end(t) + 1 , N , val , 1);
add(end(r) + 1 , end(t) + 1 , N , val , -1);
}
}
else{
add(dfn[l] , dfn[r] , end(r) , val , 1);
add(end(l) + 1 , dfn[r] , end(r) , val , -1);
}
}
for(int i = 1 ; i <= Q ; ++i){
que[i].l = dfn[read()];
que[i].r = dfn[read()];
que[i].K = read();
if(que[i].l > que[i].r)
swap(que[i].l , que[i].r);
que[i].ind = i;
}
sort(lsh + 1 , lsh + M + 1);
sort(cng + 1 , cng + cntP + 1);
sort(que + 1 , que + Q + 1);
cntL = unique(lsh + 1 , lsh + M + 1) - lsh;
for(int i = 1 ; i <= cntP ; ++i)
cng[i].val = lower_bound(lsh + 1 , lsh + cntL , cng[i].val) - lsh;
solve(1 , Q , 1 , cntP , 1 , cntL - 1);
for(int i = 1 ; i <= Q ; ++i)
printf("%d\n" , lsh[ans[i]]);
return 0;
}
菜肴制作(贪心、拓扑排序)
显然这是一个求字典序最小的拓扑序的问题,这里的字典序指的是每一个点所在位置。
值得注意的是如果正序拓扑,你必须要需要记录每一个点能够到达的所有节点的编号才能够正确贪心,显然是不可做的
正着不可做考虑倒着做,就不难想到一个正确的贪心:在反图上拓扑排序,每一次选择入度为\(0\)的点中编号最大的,放在当前序列的最前面。
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
#define PII pair < int , int >
const int MAXN = 1e5 + 7;
struct Edge{
int end , upEd;
}Ed[MAXN];
int head[MAXN] , in[MAXN] , ans[MAXN];
int N , M , cntEd;
bool vis[MAXN] , ins[MAXN];
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
++in[b];
}
priority_queue < int > q;
void tSort(){
for(int i = 1 ; i <= N ; ++i)
if(!in[i])
q.push(i);
int cnt = 0;
while(!q.empty()){
int t = q.top();
q.pop();
ans[N - cnt++] = t;
for(int i = head[t] ; i ; i = Ed[i].upEd)
if(!--in[Ed[i].end])
q.push(Ed[i].end);
}
if(cnt < N)
puts("Impossible!");
else{
for(int i = 1 ; i <= N ; ++i)
printf("%d " , ans[i]);
putchar('\n');
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in" , "r" , stdin);
freopen("out" , "w" , stdout);
#endif
for(int D = read() ; D ; --D){
N = read();
M = read();
memset(head , 0 , sizeof(head));
memset(vis , 0 , sizeof(vis));
memset(in , 0 , sizeof(in));
cntEd = 0;
for(int i = 1 ; i <= M ; ++i){
int a = read() , b = read();
addEd(b , a);
}
tSort();
}
return 0;
}
落忆枫音(拓扑排序、DP)
先不考虑生成树,那么方案就是\(\prod\limits_{i=2}^N du_i\)
里面算多的就是成环的情况,也就是\(\sum\limits_S \frac{\prod\limits_{i = 2} ^ N du_i}{\prod\limits_{i \in S} du_i}\),其中\(S\)是一个环。
注意到原图是个DAG,所以如果成环,环中必定有新加的边\((x,y)\),所以DAG中一条\(y \rightarrow x\)的路径就会对应一个环。
所以拓扑排序/记忆化搜索,在拓扑排序的过程中DP出\(\sum \frac{1}{\prod\limits_{i \in S} du_i}\)即可。
注意特判\(y=1\)的情况
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<cmath>
#include<random>
#include<cassert>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 1e5 + 7 , MOD = 1e9 + 7;
struct Edge{
int end , upEd;
}Ed[MAXN << 2];
int head[MAXN] , in[MAXN] , all[MAXN];
int x , y , N , M , cntEd , ans;
bool vis[MAXN];
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
++in[b];
}
inline int poww(long long a , int b){
int times = 1;
while(b){
if(b & 1)
times = times * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return times;
}
void dfs(int p){
if(vis[p])
return;
vis[p] = 1;
if(x == p){
all[p] = poww(in[p] , MOD - 2);
return;
}
for(int i = head[p] ; i ; i = Ed[i].upEd){
dfs(Ed[i].end);
all[p] = (all[p] + all[Ed[i].end]) % MOD;
}
all[p] = 1ll * all[p] * poww(in[p] + (p == y) , MOD - 2) % MOD;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read();
M = read();
x = read();
y = read();
for(int i = 1 ; i <= M ; ++i){
int a = read() , b = read();
addEd(a , b);
}
ans = 1;
for(int i = 2 ; i <= N ; ++i)
ans = 1ll * ans * (in[i] + (i == y)) % MOD;
if(y != 1 && y != x)
dfs(y);
cout << ans * (MOD + 1ll - all[y]) % MOD;
return 0;
}
开店(动态点分治、前缀和)
注意到这是一个动态换根统计问题,不难想到点分树解决。
建好点分树,对于每一个点维护:分治区域内所有妖怪到当前点的路径长度和、分治区域内所有妖怪到当前点在点分树上的父亲的路径长度和以及妖怪的数量,然后用点分树那套理论做。
但是如何维护妖怪年龄$ \in [L,R]$的限制?拿一个vector存下所有妖怪的年龄和对应信息,做个前缀和,每一次查询的时候在vector上把年龄区间二分出来就行了。
#include<bits/stdc++.h>
#define int long long
#define INF 0x7fffffff
#define P pair < int , int >
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
bool f = 0;
char c = getchar();
while(c != EOF && !isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(c != EOF && isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 150010;
struct Edge{
int end , upEd , len;
}Ed[MAXN << 1];
int head[MAXN] , age[MAXN] , fa[MAXN] , size[MAXN] , len[MAXN];
int ST[21][MAXN << 1] , fir[MAXN] , dep[MAXN] , logg2[MAXN << 1];
int N , Q , A , cntEd , ts , nowSize , minSize , minInd;
vector < P > cur[MAXN];
vector < int > up[MAXN];
bool vis[MAXN];
inline int abss(int a){
return a < 0 ? -a : a;
}
bool cmpp(P a , P b){
return age[a.first] < age[b.first];
}
void addEd(int , int , int);
void getSize(int);
void getRoot(int);
void init_dfz(int , int);
void init_dfs(int , int , int);
int cmp(int , int);
void init_st();
void init();
int LCA(int , int);
int calcLen(int , int);
int query(int , int , int);
signed main(){
#ifndef ONLINE_JUDGE
freopen("3241.in" , "r" , stdin);
//freopen("3241.out" , "w" , stdout);
#endif
N = read();
Q = read();
A = read();
for(int i = 1 ; i <= N ; ++i)
age[i] = read();
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read() , c = read();
addEd(a , b , c);
addEd(b , a , c);
}
init();
int lastans = 0;
for(int i = 1 ; i <= Q ; ++i){
int u = read() , a = read() , b = read();
a = (a + lastans) % A;
b = (b + lastans) % A;
if(a > b)
swap(a , b);
printf("%lld\n" , lastans = query(u , a , b));
}
return 0;
}
inline void addEd(int a , int b , int c){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
Ed[cntEd].len = c;
head[a] = cntEd;
}
void getSize(int x){
vis[x] = 1;
++nowSize;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(!vis[Ed[i].end])
getSize(Ed[i].end);
vis[x] = 0;
}
void getRoot(int x){
vis[x] = size[x] = 1;
int maxN = 0;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(!vis[Ed[i].end]){
getRoot(Ed[i].end);
maxN = max(maxN , size[Ed[i].end]);
size[x] += size[Ed[i].end];
}
maxN = max(maxN , nowSize - size[x]);
if(maxN < minSize){
minSize = maxN;
minInd = x;
}
vis[x] = 0;
}
void init_dfs(int x , int f , int l){
dep[x] = dep[f] + 1;
fir[x] = ++ts;
len[x] = l;
ST[0][ts] = x;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != f){
init_dfs(Ed[i].end , x , l + Ed[i].len);
ST[0][++ts] = x;
}
}
inline int cmp(int x , int y){
return dep[x] < dep[y] ? x : y;
}
void init_st(){
for(int i = 2 ; i <= N << 1 ; ++i)
logg2[i] = logg2[i >> 1] + 1;
for(int i = 1 ; 1 << i <= N << 1 ; ++i)
for(int j = 1 ; j + (1 << i) <= N << 1 ; ++j)
ST[i][j] = cmp(ST[i - 1][j] , ST[i - 1][j + (1 << (i - 1))]);
}
inline int LCA(int x , int y){
x = fir[x];
y = fir[y];
if(y < x)
swap(x , y);
int t = logg2[y - x + 1];
return cmp(ST[t][x] , ST[t][y - (1 << t) + 1]);
}
inline int calcLen(int x , int y){
return len[x] + len[y] - (len[LCA(x , y)] << 1);
}
void init_dfz(int x , int p){
nowSize = 0;
minSize = INF;
getSize(x);
getRoot(x);
x = minInd;
fa[x] = p;
vis[x] = 1;
for(int i = x ; i ; i = fa[i])
cur[i].push_back(P(x , 0));
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(!vis[Ed[i].end])
init_dfz(Ed[i].end , x);
sort(cur[x].begin() , cur[x].end() , cmpp);
up[x].push_back(calcLen(cur[x][0].first , fa[x] ? fa[x] : x));
cur[x][0].second = calcLen(cur[x][0].first , x);
cur[x][0].first = age[cur[x][0].first];
for(int i = 1 ; i < cur[x].size() ; ++i){
up[x].push_back(up[x][i - 1] + calcLen(cur[x][i].first , fa[x] ? fa[x] : x));
cur[x][i].second = cur[x][i - 1].second + calcLen(cur[x][i].first , x);
cur[x][i].first = age[cur[x][i].first];
}
vis[x] = 0;
}
void init(){
init_dfs(1 , 0 , 0);
init_st();
init_dfz(1 , 0);
}
inline int query(int x , int l , int r){
int sum = 0 , p = x , pastSum = 0 , pastNum = 0 , curSum , curNum;
while(x){
int t1 = lower_bound(cur[x].begin() , cur[x].end() , P(l , -1)) - cur[x].begin() , t2 = lower_bound(cur[x].begin() , cur[x].end() , P(r + 1 , -1)) - cur[x].begin();
curSum = 0;
curNum = t2 - t1;
if(--t1 >= 0)
curSum -= cur[x][t1].second;
if(--t2 >= 0)
curSum += cur[x][t2].second;
sum += curSum - pastSum;
sum += calcLen(p , x) * (curNum - pastNum);
pastSum = 0;
if(t1 >= 0)
pastSum -= up[x][t1];
if(t2 >= 0)
pastSum += up[x][t2];
pastNum = curNum;
x = fa[x];
}
return sum;
}
实验比较(树形DP)
如果没有什么思路可以先去做HEOI2013 SAO
注意到\(X_i\)两两不同意味着:将若干相同的照片看做一个点,连边\((K_{X_i} , X_i)\),那么每个点的入度一定是\(1\),只要原图没有环就一定是一个外向树森林
建一个超级源点连向所有外向树的根,那么原图就变成了一棵外向树
在这棵树上树形DP。设\(f_{i,j}\)表示对于\(i\)及其子树,能够获得长度为\(j\)的拓扑序列的方案数(注意:若干个等于号看做一个数),通过一个个合并儿子来转移:\(f_{i,j} \leftarrow f_{i,k} \times f_{son_i , l} \times g_{l,k,j}\),其中\(g_{l,k,j}\)表示两个长度为\(l,k\)的拓扑序列合并为一个长度为\(j\)的拓扑序列的方案数
不难发现\(g_{l,k,j}\)也可以DP:设\(h_{i,j,k,0/1/2}\)表示两个长度为\(i,j\)的拓扑序列合并为一个长度为\(k\)的序列且末尾为(两个拓扑序列合并得到的数/第一个拓扑序列的数/第二个拓扑序列的数)的方案数,转移枚举最后一个数由哪一个拓扑序列得来以及是否合并。
总复杂度\(O(n^3)\),分析和上面SAO是一样的
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<vector>
#include<cmath>
#include<cassert>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
const int MOD = 1e9 + 7;
struct Edge{
int end , upEd;
}Ed[107];
int head[107] , fa[107] , in[107] , be[107] , edge[107][2] , dp[107][107][107][3] , all[107][107][107];
int N , M , cnt , cntN , cntEd;
bool vis[107] , ins[107];
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
++in[b];
}
bool check(int x){
vis[x] = ins[x] = 1;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(ins[Ed[i].end])
return 1;
else
if(!vis[Ed[i].end])
if(check(Ed[i].end))
return 1;
return ins[x] = 0;
}
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
inline char getc(){
char c = getchar();
while(c == ' ' || c == '\n' || c == '\r')
c = getchar();
return c;
}
void init(){
dp[0][0][0][0] = 1;
for(int i = 0 ; i <= 100 ; ++i)
for(int j = 0 ; j <= 100 ; ++j)
for(int k = 1 ; k <= i + j ; ++k){
if(i && j)
dp[i][j][k][0] = (MOD * 4ll + dp[i - 1][j][k][2] + dp[i][j - 1][k][1] - dp[i - 1][j - 1][k - 1][0] - dp[i - 1][j - 1][k - 1][1] - dp[i - 1][j - 1][k - 1][2]) % MOD;
if(i)
dp[i][j][k][1] = (0ll + dp[i - 1][j][k - 1][0] + dp[i - 1][j][k - 1][1] + dp[i - 1][j][k - 1][2]) % MOD;
if(j)
dp[i][j][k][2] = (0ll + dp[i][j - 1][k - 1][0] + dp[i][j - 1][k - 1][1] + dp[i][j - 1][k - 1][2]) % MOD;
}
for(int i = 0 ; i <= 100 ; ++i)
for(int j = 0 ; j <= 100 ; ++j)
for(int k = 0 ; k <= 100 ; ++k)
all[i][j][k] = (0ll + dp[i][j][k][0] + dp[i][j][k][1] + dp[i][j][k][2]) % MOD;
}
int ans[107][107] , tmp[107] , sz[107];
void dfs(int x){
sz[x] = 1;
ans[x][0] = 1;
for(int i = head[x] ; i ; i = Ed[i].upEd){
dfs(Ed[i].end);
memset(tmp , 0 , sizeof(tmp));
for(int j = 0 ; j < sz[x] ; ++j)
for(int k = 1 ; k <= sz[Ed[i].end] ; ++k)
for(int q = 1 ; q <= j + k ; ++q)
tmp[q] = (tmp[q] + 1ll * all[j][k][q] * ans[x][j] % MOD * ans[Ed[i].end][k]) % MOD;
sz[x] += sz[Ed[i].end];
memcpy(ans[x] , tmp , sizeof(tmp));
}
for(int i = sz[x] ; i ; --i)
ans[x][i] = ans[x][i - 1];
ans[x][0] = 0;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read();
M = read();
for(int i = 1 ; i <= N ; ++i)
fa[i] = i;
for(int i = 1 ; i <= M ; ++i){
int a = read();
char c = getc();
int b = read();
if(c == '=')
fa[find(a)] = find(b);
else{
edge[++cnt][0] = a;
edge[cnt][1] = b;
}
}
for(int i = 1 ; i <= N ; ++i)
if(!be[find(i)])
be[find(i)] = ++cntN;
for(int i = 1 ; i <= cnt ; ++i)
addEd(be[find(edge[i][0])] , be[find(edge[i][1])]);
for(int i = 1 ; i <= cntN ; ++i)
if(!vis[i] && check(i)){
cout << 0;
return 0;
}
init();
for(int i = 1 ; i <= cntN ; ++i)
if(!in[i])
addEd(0 , i);
dfs(0);
int sum = 0;
for(int i = 1 ; i <= N + 1 ; ++i)
sum = (sum + ans[0][i]) % MOD;
cout << sum;
return 0;
}
HNOI2015做题笔记的更多相关文章
- C语言程序设计做题笔记之C语言基础知识(下)
C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...
- C语言程序设计做题笔记之C语言基础知识(上)
C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...
- SDOI2017 R1做题笔记
SDOI2017 R1做题笔记 梦想还是要有的,万一哪天就做完了呢? 也就是说现在还没做完. 哈哈哈我竟然做完了-2019.3.29 20:30
- SDOI2014 R1做题笔记
SDOI2014 R1做题笔记 经过很久很久的时间,shzr又做完了SDOI2014一轮的题目. 但是我不想写做题笔记(
- SDOI2016 R1做题笔记
SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...
- LCT做题笔记
最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...
- java做题笔记
java做题笔记 1. 初始化过程是这样的: 1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化: 2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序 ...
- SAM 做题笔记(各种技巧,持续更新,SA)
SAM 感性瞎扯. 这里是 SAM 做题笔记. 本来是在一篇随笔里面,然后 Latex 太多加载不过来就分成了两篇. 标 * 的是推荐一做的题目. trick 是我总结的技巧. I. P3804 [模 ...
- PKUWC/SC 做题笔记
去年不知道干了些啥,什么省选/营题都没做. 现在赶应该还来得及(?) 「PKUWC2018」Minimax Done 2019.12.04 9:38:55 线段树合并船新玩法??? \(O(n^2)\ ...
随机推荐
- 使用CLion在MacOS、Linux上编译C++代码
推荐: http://www.cnblogs.com/conorpai/p/6425048.html
- 如何解决用CMake未定义引用`JNI_CreateJavaVM'?
我需要从C ++运行Java,一般来说问题已经解决,但我的make系统或脚本出了问题,有一个创建JVM的C ++文件: #include <jni.h> #include <iost ...
- .Net Core(一)环境搭建与基本使用
.Net Core(一)环境搭建与基本使用 一.系统配置 a) Linux下如果想要打开类似任务管理器,可以使用top命令,在控制台会动态刷新CPU和内存占用.进程等信息.vmstat和free命令可 ...
- (后端)如何将数据库的表导出生成Excel?
1.如何通过元数据拿到数据库的信息? 2.如何用Java生成Excel表? 3.将数据库中的表导出生成Excel案例 如何通过元数据拿到数据库的信息 元数据:描述数据的数据 Java中使用元数据的两个 ...
- Excel函数进阶
#笔记:为了方便自己以后查找,以便随时随地能查看.形成系统化学习! 查找引用函数 ------------------包含----------Vlookup函数(if数组).Hlookup函数.loo ...
- ubuntu 配置拼音输入法步骤
今天配置了一下 ubuntu 拼音,要求使用ubuntu 内置拼音.大致步骤我记录一下: 配置拼音,使用 ibus pinyin,网上有很多帖子大致步骤: 1)安装 中文语言 2)安装ibus 3) ...
- centos 6.5下安装nmap工具及简单用法
Nmap是一款针对大型网络的端口扫描工具,被广泛应用于黑客领域做漏洞探测以及安全扫描,其主要功能有主机发现(Host Discovery). 端口扫描(Port Scanning). 版本侦测(Ver ...
- mysql启动失败又一例
搭的wordpress报错: 后台用的mysql,之前也崩过,原因是虚拟内存耗尽,通过增加swap空间最终让数据重新启动. 但仅过一晚上,数据库再次崩溃.看来要查一查是什么程序耗尽资源. 执行top, ...
- Python实例---模拟微信网页登录(day4)
第五步: 获取联系人信息---day4代码 settings.py """ Django settings for weixin project. Generated b ...
- VMware虚拟机中CentOS 7的硬盘空间扩容
查看centos7系统挂载点信息 扩展VMWare-centos7硬盘空间 对新增加的硬盘进行分区.格式化 添加新LVM到已有的LVM组,实现扩容 1.查看centos7系统挂载点信息 df -h查看 ...