T1 完美塔防
有一些空地,一些障碍,一些炮台,一些反射镜
障碍会挡住炮台的炮,
反射镜可以 90° 反射炮台的光线,炮台可以选择打他所在的水平一条线或者竖直一条线
求是否有一组方案满足每个空地必须要被至少一个炮台打到,且每个炮台都不能被炮台打到
用 $n \times m$ 的字符矩形给出,有方案的话需要给出构造
$n,m \leq 50$
有 $200$ 组测试数据
sol:
考场上看到这题肯定是先想网络流,上下界建出来发现...我建的某一种边要求“流 $0$ 或者流 $2$”
然后我就不会了,考完发现这是 2-sat 裸题...
如果知道 2-sat 就不用那么麻烦了,可以一开始可以预处理出每个炮台必须打的方向和每个炮台不能打的方向
然后发现如此操作后每个空地最多被两个炮台攻击,则这个空地相当于一个条件“选 $A$ 或者选 $B$”
然后就是 2-sat 了
#include#include #include #include #include using namespace std; typedef long long LL; #define pb push_back const int maxn = 505; int dx[]={-1,0,1,0}; int dy[]={0,1,0,-1}; vector<int> adj[maxn],ebk[maxn],cov[maxn][maxn]; char str[maxn][maxn];int R,C,T; bool _cov[maxn][maxn]; bool cov0[maxn][maxn]; bool cov1[maxn][maxn]; int id[maxn][maxn],tot,scc[maxn],iscc; int dfn[maxn],low[maxn],idx; int stk[maxn],top;bool ins[maxn]; int que[maxn],head,tail,deg[maxn],pos[maxn]; #define clr(_) memset(_,0,sizeof _) bool inG(int x,int y) { return x>=1&&x<=R&&y>=1&&y<=C&&str[x][y]!='#'; } bool beam(int x,int y) { return str[x][y]=='|'||str[x][y]=='-'; } bool dfs(bool cov[maxn][maxn],int x,int y,int d) { cov[x][y]=true; if (!inG(x+dx[d],y+dy[d])) return true; int nx=x+dx[d],ny=y+dy[d]; if (beam(nx,ny)) return false; if (str[nx][ny]=='.') return dfs(cov,nx,ny,d); else return dfs(cov,nx,ny,d^(str[nx][ny]=='/'?1:3)); } void tarjan(int u) { dfn[u]=low[u]=++idx; stk[++top]=u;ins[u]=true; for (unsigned j=0;j ) { int v=adj[u][j]; if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]); else if (ins[v]) low[u]=min(low[u],dfn[v]); } if (low[u]!=dfn[u]) return ; scc[u]=++iscc; while (stk[top]!=u) { int v=stk[top--]; ins[v]=false;scc[v]=iscc; } ins[u]=false;top--; } void solve() { scanf("%d %d",&R,&C); for (int i=1;i<=R;i++) scanf("%s",str[i]+1); for (int i=1;i<=tot*2+1;i++) adj[i].clear(),ebk[i].clear(); for (int i=1;i<=R;i++) for (int j=1;j<=C;j++) cov[i][j].clear(); clr(_cov);clr(dfn);tot=idx=iscc=0; for (int i=1;i<=R;i++) for (int j=1;j<=C;j++) if (beam(i,j)) { id[i][j]=++tot; clr(cov0);bool tag0=dfs(cov0,i,j,1)&dfs(cov0,i,j,3); clr(cov1);bool tag1=dfs(cov1,i,j,0)&dfs(cov1,i,j,2); if (!tag0&&!tag1) {printf("IMPOSSIBLE\n");return ;} if (!tag0) { for (int x=1;x<=R;x++) for (int y=1;y<=C;y++) if (str[x][y]=='.'&&cov1[x][y]) cov[x][y].pb(tot<<1|1); adj[tot<<1].pb(tot<<1|1); } else if (!tag1) { for (int x=1;x<=R;x++) for (int y=1;y<=C;y++) if (str[x][y]=='.'&&cov0[x][y]) cov[x][y].pb(tot<<1); adj[tot<<1|1].pb(tot<<1); } else { for (int x=1;x<=R;x++) for (int y=1;y<=C;y++) if (str[x][y]=='.') { if (cov0[x][y]&&cov1[x][y]) _cov[x][y]=true; else if (cov0[x][y]) cov[x][y].pb(tot<<1); else if (cov1[x][y]) cov[x][y].pb(tot<<1|1); } } } for (int i=1;i<=R;i++) for (int j=1;j<=C;j++) if (str[i][j]=='.'&&!_cov[i][j]) { int siz=cov[i][j].size(),u,v; if (siz==0) {printf("IMPOSSIBLE\n");return ;} else if (siz==1) u=cov[i][j][0],adj[u^1].pb(u); else if (siz==2) u=cov[i][j][0],v=cov[i][j][1],adj[u^1].pb(v),adj[v^1].pb(u); } for (int i=2;i<=(tot<<1|1);i++) if (!dfn[i]) tarjan(i); for (int i=1;i<=tot;i++) if (scc[i<<1]==scc[i<<1|1]) {printf("IMPOSSIBLE\n");return ;} for (int v,u=2;u<=(tot<<1|1);u++) for (unsigned j=0;j ) if (scc[v=adj[u][j]]!=scc[u]) ebk[scc[v]].pb(scc[u]),deg[scc[u]]++; head=1;tail=0; for (int i=1;i<=iscc;i++) if (!deg[i]) que[++tail]=i; while (head<=tail) { int u=que[head];pos[u]=head++; for (unsigned j=0;j ) { int v=ebk[u][j]; if (!--deg[v]) que[++tail]=v; } } for (int i=1;i<=tot*2+1;i++) ebk[i].clear(),adj[i].clear(); printf("POSSIBLE\n"); } int main(){ for (cin >> T; T; T--) solve(); }
T2 生成树计数
给一个 $n$ 个点的无向带边权完全图 $G$,求 $\sum\limits_{T 是 G 的生成树} \space \space \space \space \space (\sum\limits_{i \in E_T} w_i)^k$,膜 $998244353$
$n,k \leq 30$
sol:
考场上很 naive 的以为矩阵树定理求的是边权和,然而求的是边权乘积...
于是大概就是要把一个和的 $k$ 次方转化成积
然后就有一个公式 $(\sum\limits_{i=1}^m w_i)^k = k! \times [z^k] \prod\limits_{i=1}^m e^{w_i \times z} = k! \times [z^k] \prod\limits_{i=1}^m \sum\limits_{t} \frac{w_i^t \times z^t}{t!}$
然后从它里面提取出一个“边权乘积”,即 $\prod\limits_{i=1}^m \sum\limits_{t} \frac{w_i^t \times z^t}{t!}$
所以“边权”就是 $\sum\limits_{t} \frac{w_i^t \times z^t}{t!}$ 由于只需要第 $k$ 项,这个和式的 $t$ 取到 $[0,k]$ 就可以了
然后就每条边维护一下这个东西,再矩阵树定理即可
因为数据比较小,多项式部分暴力就可以了
复杂度 $O(n^3 \times k^2)$
#include#define LL long long #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f; for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0'; return x * f; } const int maxn = 32, mod = 998244353; int n, k, ifac[maxn]; int a[maxn][maxn][maxn], mt[maxn][maxn], res[maxn], _inv[maxn], _tmp[maxn], _dec[maxn]; inline int mo(int x) { return ((x % mod) + mod) % mod; } inline int inc(int x, int y) { x += y; if(x >= mod) x -= mod; return x; } inline int dec(int x, int y) { x -= y; if(x < 0) x += mod; return x; } inline int mul(int x, int y) { return 1LL * x * y % mod; } inline int ksm(int x, int t, int res = 1) { for(; t; x = mul(x, x), t = t >> 1) if(t & 1) res = mul(res, x); return res; } #define inv(x) (ksm(x, mod-2)) inline void mul(int *a, int *b, int *res) { static int tmp[maxn]; memset(tmp, 0, sizeof(tmp)); rep(i, 0, k) rep(j, 0, k-i) tmp[i + j] = inc(tmp[i + j], mul(a[i], b[j])); memcpy(res, tmp, sizeof(tmp)); } inline void inverse(int *a, int *res) { static int tmp[maxn], temp[maxn]; memset(tmp, 0, sizeof(tmp)); tmp[0] = inv(a[0]); rep(i, 1, k) temp[i] = mul(a[i], tmp[0]); rep(i, 1, k) rep(j, 1, i) tmp[i] = inc(tmp[i], mul(dec(0, temp[j]), tmp[i - j])); memcpy(res, tmp, sizeof(tmp)); } int main() { n = read(), k = read(); ifac[1] = ifac[0] = 1; rep(i, 2, k) ifac[i] = mul(dec(mod, mod / i), ifac[mod % i]); rep(i, 1, k) ifac[i] = mul(ifac[i], ifac[i - 1]); rep(i, 1, n) rep(j, 1, n) mt[i][j] = read(); rep(i, 1, n) rep(j, 1, i-1) { int pw = 1; rep(l, 0, k) { a[i][j][l] = a[j][i][l] = dec(0, mul(pw, ifac[l])); a[i][i][l] = inc(a[i][i][l], mul(pw, ifac[l])); a[j][j][l] = inc(a[j][j][l], mul(pw, ifac[l])); pw = mul(pw, mt[i][j]); } } n--; res[0] = 1; rep(i, 1, n) { inverse(a[i][i], _inv); rep(kk, i+1, n) { mul(_inv, a[kk][i], _tmp); rep(l, i, n) { mul(_tmp, a[i][l], _dec); rep(t, 0, k) a[kk][l][t] = dec(a[kk][l][t], _dec[t]); } } } rep(i, 1, n) mul(res, a[i][i], res); int ans = res[k]; rep(i, 1, k) ans = mul(ans, i); cout << ans << endl; }
T3 图的难题
给一个图,如果存在一种染色方案把图染成黑白两色,黑色子图没有环,白色子图没有环,则称这个图合法,给一个图判断合不合法
$n \leq 500,m \leq 1000$
sol:
大力猜结论
如果一个非空子图不合法,则整个图都不合法
一个子图合法当且仅当 $e \leq 2 \times (v-1)$
意会一下,发现大概很对(大概能拆成两个树,大概多一条边就成环了)
然后令点的权为 $-2$,边的权为 $1$,每次堵住一条边然后最大权闭合图即可
堵住一条边就是先算一下这条边已经流了多少,把这些流全退了,再跑一遍最大流
这样做是因为裸跑最大权闭合图的话算出来肯定是 $0$ (空图)而我们要强制非空
#include#define LL long long #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f; for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0'; return x * f; } const int maxn = 100010; int n, m, S, T; struct Dinic { int m, s, t; int vis[maxn], _tim, dis[maxn]; queue<int> q; int cur[maxn], head[maxn], nx[maxn * 5]; int flow; void init(int n) { memset(head, -1, sizeof(head)); memset(cur, -1, sizeof(cur)); memset(vis, 0, sizeof(vis)); flow = 0; m = 0; _tim = 0; } struct Edge { int from, to, caps; Edge() {} Edge(int _u, int _v, int _w) : from(_u), to(_v), caps(_w){} }es[maxn * 5]; inline void add(int u, int v, int w) { es[m] = Edge(u, v, w); nx[m] = head[u]; head[u] = m++; es[m] = Edge(v, u, 0); nx[m] = head[v]; head[v] = m++; } int BFS() { vis[t] = ++_tim; dis[t] = 0; q.push(t); while(!q.empty()) { int now = q.front(); q.pop(); cur[now] = head[now]; for(int i=head[now];~i;i=nx[i]) { Edge &e = es[i ^ 1]; if(e.caps && (vis[e.from] != _tim)) { q.push(e.from); vis[e.from] = _tim; dis[e.from] = dis[now] + 1; } } } return (vis[s] == _tim); } int DFS(int u, int a) { //cout << u << endl; if(u == t || !a) return a; int flow = 0, f; for(int &i=cur[u];~i;i=nx[i]) { Edge &e = es[i]; if(dis[e.to] == dis[u] - 1 && (f = DFS(e.to, min(e.caps, a)))) { flow += f; e.caps -= f; es[i ^ 1].caps += f; a -= f; if(!a) return flow; } } return flow; } int MaxFlow(int _s, int _t, int mx = 2147483233) { s = _s, t = _t; int flw = 0; while(BFS() && mx) flw += DFS(s,mx), mx -= flw; if(s == S) return (flow += flw); return flw; } void block(int pos) { int mx = es[2 * (pos - 1) + 1].caps; int curflow = MaxFlow(T, pos, mx); flow -= curflow; } } sol; int u[maxn], v[maxn]; int solve() { n = read(), m = read(); sol.init(n + m + 2); S = n + m + 1, T = n + m + 2; rep(i, 1, m) u[i] = read(), v[i] = read(); rep(i, 1, n) sol.add(S, i, 2); rep(i, 1, m) { sol.add(i + n, T, 1); sol.add(v[i], i + n, 2147483233); sol.add(u[i], i + n, 2147483233); } rep(i, 1, n) { sol.block(i); sol.es[2 * (i - 1)].caps = sol.es[2 * (i - 1) + 1].caps = 0; if(m > sol.MaxFlow(S, T)) return 0; sol.es[2 * (i - 1)].caps = 2; } return 1; } int main() { dwn(T, read(), 1) puts(solve() ? "Yes" : "No"); return 0; }