HNOI2015
亚瑟王(概率DP)
根据期望的线性性,我们只需要算出每一种卡牌触发的概率就可以算出期望的值
考虑与第(i)张卡牌触发概率相关的量,除了(p_i)还有前(i-1)张卡牌中触发过的卡牌的数量。
假设前(i)张卡牌中触发了(j)张的概率为(f_{i,j}),那么第(i)张卡牌的触发概率就是(sum f_{i-1,j} imes (1 - (1 - p_i)^{R - j}))
一个不好理解的地方:对于某一张卡牌,它触发的概率与之前卡牌在哪一个回合触发无关,只与数量有关系,触发的数量决定可能会触发这一张卡牌的回合数。
所以我们需要求的是(f_{i,j})。不难设计出(f_{i,j})的DP转移:(f_{i,j} = f_{i-1,j} imes (1 - p_i) ^ {R - j} + f_{i-1,j-1} imes (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) eq 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
" , 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('
');
}
}
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)
先不考虑生成树,那么方案就是(prodlimits_{i=2}^N du_i)
里面算多的就是成环的情况,也就是(sumlimits_S frac{prodlimits_{i = 2} ^ N du_i}{prodlimits_{i in S} du_i}),其中(S)是一个环。
注意到原图是个DAG,所以如果成环,环中必定有新加的边((x,y)),所以DAG中一条(y ightarrow x)的路径就会对应一个环。
所以拓扑排序/记忆化搜索,在拓扑排序的过程中DP出(sum frac{1}{prodlimits_{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
" , 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} imes f_{son_i , l} imes 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 == '
' || c == '
')
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;
}