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做题笔记的更多相关文章

  1. C语言程序设计做题笔记之C语言基础知识(下)

    C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...

  2. C语言程序设计做题笔记之C语言基础知识(上)

    C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...

  3. SDOI2017 R1做题笔记

    SDOI2017 R1做题笔记 梦想还是要有的,万一哪天就做完了呢? 也就是说现在还没做完. 哈哈哈我竟然做完了-2019.3.29 20:30

  4. SDOI2014 R1做题笔记

    SDOI2014 R1做题笔记 经过很久很久的时间,shzr又做完了SDOI2014一轮的题目. 但是我不想写做题笔记(

  5. SDOI2016 R1做题笔记

    SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...

  6. LCT做题笔记

    最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...

  7. java做题笔记

    java做题笔记 1. 初始化过程是这样的: 1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化: 2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序 ...

  8. SAM 做题笔记(各种技巧,持续更新,SA)

    SAM 感性瞎扯. 这里是 SAM 做题笔记. 本来是在一篇随笔里面,然后 Latex 太多加载不过来就分成了两篇. 标 * 的是推荐一做的题目. trick 是我总结的技巧. I. P3804 [模 ...

  9. PKUWC/SC 做题笔记

    去年不知道干了些啥,什么省选/营题都没做. 现在赶应该还来得及(?) 「PKUWC2018」Minimax Done 2019.12.04 9:38:55 线段树合并船新玩法??? \(O(n^2)\ ...

随机推荐

  1. :hover在ios无效问题

    :hover 设置的样式在ios显示不出来,需要在按钮元素或body/html上绑定一个touchstart事件才能激活:active状态. 解决方案: 方案1 js绑定: document.body ...

  2. Git应用—03分支管理和冲突解决(转载)

    Git 分支管理和冲突解决 https://www.cnblogs.com/mengdd/p/3585038.html 创建分支 git branch 没有参数,显示本地版本库中所有的本地分支名称. ...

  3. loadrunner 脚本开发-文件读写操作

    脚本开发-文件读写操作 by:授客 QQ:1033553122 函数说明 函数原型: size_t fwrite( const void *buffer, size_t size, size_t co ...

  4. Kotlin入门(3)基本变量类型的用法

    上一篇文章介绍了Kotlin在App开发中的简单用法,包括操纵控件对象.设置控件监听器,以及弹出Toast提示等等.也许大家已经迫不及待想要了解更深入的App开发,可是由于Kotlin是一门全新的语言 ...

  5. loadrunner录制的时候如何应对验证码的问题解决办法?

    对这个问题,我个人的看法是,基本上可以考虑从三个途径来解决该问题: 1.第一种方法,也是最容易想到的,在被测系统中暂时屏蔽验证功能,也就是说,临时修改应用,无论用户输入的是什么验证码,都认为是正确的. ...

  6. 13.2、进程的通信:Queue、Pipe、

    内容相关: 概念:进程的通信 Queue:创建与使用 Pipe:创建与使用 进程通信的概念 进程的资源空间是相互独立的,一般而言是不能相互访问的.但很多情况下进程间需要互相通信,来完成系统的某项功能. ...

  7. [20180503]珅与分隔符.txt

    [20180503]珅与分隔符.txt --//今天再次遇到分隔符问题,以前也遇到过做一次,链接.http://blog.itpub.net/267265/viewspace-763696/--//这 ...

  8. web service && WCF 学习小结

    Web Service和WCF技术都提供了应用程序与应用程序之间的通信.它们都是基于soap消息在客户端和服务端之间进行通信,由于soap消息是一种xml格式,因此传输的数据格式为XML.每次客户端向 ...

  9. nodejs在spawn中执行npm报错 [Error: spawn ENOENT]” errors

    描述: 上代码 var ps = require('child_process').spawn("npm", ['install'], { stdio: 'inherit', cw ...

  10. OSPF单区域配置

    OSPF单区域配置 实验环境:华为模拟器eNSP 现在有这样一个拓扑图: 我想要让R1可以ping通R3,显然目前是不行的: <R1>ping 192.168.2.2 PING 192.1 ...