From 134b1bc039fe0a526f53d6deef1a751090c69325 Mon Sep 17 00:00:00 2001 From: Neurotical <115883114+Neurotical@users.noreply.github.com> Date: Sat, 7 Sep 2024 08:26:31 +0000 Subject: [PATCH] =?UTF-8?q?add=20=E5=8D=95=E8=B0=83=E6=A0=88=20=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E6=B5=81=202-sat=20=E5=B7=AE=E5=88=86=E7=BA=A6?= =?UTF-8?q?=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...60\346\215\256\347\273\223\346\236\204.md" | 37 ++ "5-\345\233\276\350\256\272.md" | 471 +++++++++++++++++- "8-\347\275\221\347\273\234\346\265\201.md" | 229 +++++++++ 3 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 "8-\347\275\221\347\273\234\346\265\201.md" diff --git "a/2-\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/2-\346\225\260\346\215\256\347\273\223\346\236\204.md" index fcf04d7..0ffd620 100644 --- "a/2-\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/2-\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -327,7 +327,14 @@ int main() return 0; } ``` +## 线段树上二分 +eg. 两种操作,1. 修改ai为d 2. 查询l,r中第一次出现大于等于d位置,否则返回-1 +维护区间最大值, + +对一个区间判断最大值是否大于等于d + +存在则先找左区间,再找右区间 ## 区间最值线段树 @@ -391,8 +398,38 @@ int query(int l, int r, int cl = 1, int cr = n, int p = 1) } } ``` +## 单调栈 +满足栈中元素单调递增或递减的栈 + +可用于o(n)寻找每个数之后第一个大于他的数的位置(用单调递减栈) + +可解决求 $max_{1\leq l\leq n,l\leq r \leq n}((r-l+1)*min_{l\leq i \leq r}a[i])$ + +即区间长度乘区间最值结果的最值 + +``` cpp +int n,m;//洛谷P5788 +stackst; +int a[3000005],ans[3000005]; +void solve(){ + cin>>n; + for(int i=1;i<=n;i++) cin>>a[i]; + for(int i=1;i<=n;i++){ + while(st.size()&&a[i]>a[st.top()]){//维护递减栈 + ans[st.top()] = i;//更新答案 + st.pop(); + } + st.push(i); + } + for(int i=1;i<=n;i++) cout<>n>>m; +vector>e; +for(int i=1;i<=m;i++){ + int l,r,w;cin>>l>>r>>w; + e.push_back({l,r,w}); +} +for(int i=0;i<=n;i++){ + for(auto [l,r,w]:e){ + x[l] = min(x[l],x[r]+w); + } +} +for(auto [l,r,w]:e){ + if(x[l]>x[r]+w){ + cout<<-1<= $x_i$ - w; + +存储部分不变 + +迭代代码变为: + +```cpp +for (int i=0;i<=n;i++){ + for(auto [l,r,w]:e){ + x[r] = max(x[r],x[l]-w); + } +} +``` + +迭代部分也可利用spfa或dijstra优化 + +例: +#### 题目描述 + +给出一组包含 $m$ 个不等式,有 $n$ 个未知数的形如: + +$$ \begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases}$$ + +的不等式组,求任意一组满足这个不等式组的解。 + +#### 输入格式 + +第一行为两个正整数 $n,m$,代表未知数的数量和不等式的数量。 + +接下来 $m$ 行,每行包含三个整数 $c,c',y$,代表一个不等式 $x_c-x_{c'}\leq y$。 + +#### 输出格式 + +一行,$n$ 个数,表示 $x_1 , x_2 \cdots x_n$ 的一组可行解,如果有多组解,请输出任意一组,无解请输出 `NO`。 + +#### 样例 #1 + +##### 样例输入 #1 + +``` +3 3 +1 2 3 +2 3 -2 +1 3 1 +``` + +##### 样例输出 #1 + +``` +5 3 5 +``` + +**数据范围** + +对于 $100\%$ 的数据,$1\leq n,m \leq 5\times 10^3$,$-10^4\leq y\leq 10^4$,$1\leq c,c'\leq n$,$c \neq c'$。 + +#### 题解 + +```cpp +#include +#define ll long long +#define endl '\n' +#define rep(i,a,b) for(ll i=a;i<=b;++i) +using namespace std; +#define int long long +// const ll maxn=10+1e5; +const long long mod = 998244353; + +const int N = 5010; +const int M = 5010; +int n,m; +vector>e; +int x[5010]; +void solve() +{ + cin>>n>>m; + for(int i=1;i<=m;i++){ + int u,v,w; + cin>>u>>v>>w; + e.push_back({u,v,w}); + } + for(int i=1;i<=n;i++){ + for(auto [u,v,w]:e){ + x[u] = min(x[u],x[v]+w); + } + } + for(auto [u,v,w]:e){ + if(x[u]>x[v]+w){ + cout<<"NO"<>T; + while(T--){ + solve(); + } +} +``` + +## 2-sat +2-SAT,简单的说就是给出n个集合,每个集合有两个元素,已知若干个,表示 a与b矛盾(其中a与 b属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 n个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。 + +eg.[P4782 【模板】2-SAT - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P4782) + +### 【模板】2-SAT + +#### 题目描述 + +有 $n$ 个布尔变量 $x_1$$\sim$$x_n$,另有 $m$ 个需要满足的条件,每个条件的形式都是 「$x_i$ 为 `true` / `false` 或 $x_j$ 为 `true` / `false`」。比如 「$x_1$ 为真或 $x_3$ 为假」、「$x_7$ 为假或 $x_2$ 为假」。 + +2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。 + +#### 输入格式 + +第一行两个整数 $n$ 和 $m$,意义如题面所述。 + +接下来 $m$ 行每行 $4$ 个整数 $i$, $a$, $j$, $b$,表示 「$x_i$ 为 $a$ 或 $x_j$ 为 $b$」($a, b\in \{0,1\}$) + +#### 输出格式 + +如无解,输出 `IMPOSSIBLE`;否则输出 `POSSIBLE`。 + +下一行 $n$ 个整数 $x_1\sim x_n$($x_i\in\{0,1\}$),表示构造出的解。 + +#### 样例 #1 + +##### 样例输入 #1 + +``` +3 1 +1 1 3 0 +``` + +##### 样例输出 #1 + +``` +POSSIBLE +0 0 0 +``` + +#### 提示 + +$1\leq n, m\leq 10^6$ , 前 $3$ 个点卡小错误,后面 $5$ 个点卡效率。 + +由于数据随机生成,可能会含有( 10 0 10 0)之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里。 + +#### 思路 + +对[i,a,j,b] + +化成两条边 $\lnot i \to j$ 、 $\lnot j \to j$ + + 对建好的图跑tarjan + +若最终存在 i 使得 $\lnot i$和i在同一个scc中,则无解,否则取拓扑序中靠后的元素 + +#### 题解 + +```cpp +#include +#define ll long long +#define endl '\n' +#define rep(i,a,b) for(ll i=a;i<=b;++i) +using namespace std; +#define int long long +// const ll maxn=10+1e5; +const long long mod = 998244353; + +const int N = 2000005; +const int M = 2000005; +vectore[N]; +int col[N],dfn[N],low[N],cnt; +int stk[N],top,_cnt; +vectorans[N]; + +int n,m; +void Tarjan(int u){ + dfn[u]=low[u]=++cnt; + stk[++top]=u; + for(auto v:e[u]){ + if(!dfn[v]) Tarjan(v),low[u]=min(low[u],low[v]); + else if(!col[v]) low[u]=min(low[u],dfn[v]); + } + if(low[u]==dfn[u]){ + col[u]=++_cnt; + ans[_cnt].push_back(u); + while(stk[top]!=u) + ans[_cnt].push_back(stk[top]),col[stk[top--]]=_cnt; + --top; + } +} + +void solve() +{ cin>>n>>m; + for(int i=1;i<=m;i++){ + int u,a,v,b;cin>>u>>a>>v>>b; + u--;v--; + u=2*u+a; + v=2*v+b; + e[u^1].push_back(v); + e[v^1].push_back(u); + } + for(int i=0;i<2*n;i++){ + if(!dfn[i]){ + Tarjan(i); + } + } + for(int i=0;i<2*n;i+=2){ + if(col[i]==col[i^1]){ + cout<<"IMPOSSIBLE"<>T; + while(T--){ + solve(); + } +} +``` + +### [JSOI2010] 满汉全席 + +#### 题目描述 + +满汉全席是中国最丰盛的宴客菜肴,有许多种不同的材料透过满族或是汉族的料理方式,呈现在数量繁多的菜色之中。由于菜色众多而繁杂,只有极少数博学多闻技艺高超的厨师能够做出满汉全席,而能够烹饪出经过专家认证的满汉全席,也是中国厨师最大的荣誉之一。世界满汉全席协会是由能够料理满汉全席的专家厨师们所组成,而他们之间还细分为许多不同等级的厨师。 + +为了招收新进的厨师进入世界满汉全席协会,将于近日举办满汉全席大赛,协会派遣许多会员当作评审员,为的就是要在参赛的厨师之中,找到满汉界的明日之星。 + +大会的规则如下:每位参赛的选手可以得到 $n$ 种材料,选手可以自由选择用满式或是汉式料理将材料当成菜肴。 + +大会的评审制度是:共有 $m$ 位评审员分别把关。每一位评审员对于满汉全席有各自独特的见解,但基本见解是,要有两样菜色作为满汉全席的标志。如某评审认为,如果没有汉式东坡肉跟满式的涮羊肉锅,就不能算是满汉全席。但避免过于有主见的审核,大会规定一个评审员除非是在认为必备的两样菜色都没有做出来的状况下,才能淘汰一位选手,否则不能淘汰一位选手。 + +换句话说,只要参赛者能在这两种材料的做法中,其中一个符合评审的喜好即可通过该评审的审查。如材料有猪肉,羊肉和牛肉时,有四位评审员的喜好如下表: + +``` +评审一 评审二 评审三 评审四 +满式牛肉 满式猪肉 汉式牛肉 汉式牛肉 +汉式猪肉 满式羊肉 汉式猪肉 满式羊肉 +``` + +如参赛者甲做出满式猪肉,满式羊肉和满式牛肉料理,他将无法满足评审三的要求,无法通过评审。而参赛者乙做出汉式猪肉,满式羊肉和满式牛肉料理,就可以满足所有评审的要求。 + +但大会后来发现,在这样的制度下如果材料选择跟派出的评审员没有特别安排好的话,所有的参赛者最多只能通过部分评审员的审查而不是全部,所以可能会发生没有人通过考核的情形。 + +如有四个评审员喜好如下表时,则不论参赛者采取什么样的做法,都不可能通过所有评审的考核: + +``` +评审一 评审二 评审三 评审四 +满式羊肉 满式猪肉 汉式羊肉 汉式羊肉 +汉式猪肉 满式羊肉 汉式猪肉 满式猪肉 +``` + +所以大会希望有人能写一个程序来判断,所选出的 $m$ 位评审,会不会发生没有人能通过考核的窘境,以便协会组织合适的评审团。 + +#### 输入格式 + +第一行包含一个数字 $K$($1\le K \le 50$),代表测试文件包含了 $K$ 组数据。 + +每一组测试数据的第一行包含两个数字 $n$ 跟 $m$($n≤100$,$m≤1000$),代表有 $n$ 种材料,$m$ 位评审员。 + +为方便起见,舍弃做法的中文名称而给予编号,编号分别从 $1$ 到 $n$。 + +接下来的 $m$ 行,每行都代表对应的评审员所拥有的两个喜好,每个喜好由一个英文字母跟一个数字代表,如 $m1$ 代表这个评审喜欢第 $1$ 个材料透过满式料理做出来的菜,而 $h2$ 代表这个评审员喜欢第 $2$ 个材料透过汉式料理做出来的菜。 + +#### 输出格式 + +每组测试数据输出一行,如果不会发生没有人能通过考核的窘境,输出 ```GOOD```;否则输出 ```BAD```(均为大写字母)。 + +#### 样例 #1 + +##### 样例输入 #1 + +``` +2 +3 4 +m3 h1 +m1 m2 +h1 h3 +h3 m2 +2 4 +h1 m2 +m2 m1 +h1 h2 +m1 h2 +``` + +##### 样例输出 #1 + +``` +GOOD +BAD +``` + +#### 题解 + +``` cpp +#include +#define ll long long +#define endl '\n' +#define rep(i,a,b) for(ll i=a;i<=b;++i) +using namespace std; +#define int long long +// const ll maxn=10+1e5; +const long long mod = 998244353; + +const int N = 2010; +const int M = 2010; +vectore[N]; +int col[N],dfn[N],low[N],cnt; +int stk[N],top,_cnt; +vectorans[N]; + +int n,m; +void Tarjan(int u){ + dfn[u]=low[u]=++cnt; + stk[++top]=u; + for(auto v:e[u]){ + if(!dfn[v]) Tarjan(v),low[u]=min(low[u],low[v]); + else if(!col[v]) low[u]=min(low[u],dfn[v]); + } + if(low[u]==dfn[u]){ + col[u]=++_cnt; + ans[_cnt].push_back(u); + while(stk[top]!=u) + ans[_cnt].push_back(stk[top]),col[stk[top--]]=_cnt; + --top; + } +} + +void solve() +{ cin>>n>>m; + cnt=0;_cnt=0;top=0; + for(int i=0;i<2*n;i++){ + e[i].clear(); + dfn[i]=0; + low[i]=0; + col[i]=0; + ans[i].clear(); + } + for(int i=1;i<=m;i++){ + char x,y;int u,v; + cin>>x; + cin>>u; + cin>>y; + cin>>v; + u--;v--;//0 - n-1 + //cout<>T; + while(T--){ + solve(); + } +} +``` diff --git "a/8-\347\275\221\347\273\234\346\265\201.md" "b/8-\347\275\221\347\273\234\346\265\201.md" new file mode 100644 index 0000000..ba8f2b2 --- /dev/null +++ "b/8-\347\275\221\347\273\234\346\265\201.md" @@ -0,0 +1,229 @@ +## 最大流 +### dinic + +初始化 g.init(s,t,num) + +先通过bfs对残余网络进行分层 + +根据**层次**反复 dfs遍历**残量网络**,一次 dfs找到一条**增广路**并更新,直至跑完能以当前层次到达 TT 的所有路径。 + +一般可解决1e5以内问题 + +``` cpp +const int V = 1010; +const int E = 101000; +template +struct FlowGraph{ + int s,t,vtot; + int head[V],etot; + int dis[V],cur[V]; + struct edge{ + int v,nxt; + T f; + }e[E*2]; + void addedge(int u,int v,T f){ + e[etot] = {v,head[u],f};head[u] = etot++; + e[etot] = {u,head[v],0};head[v] = etot++; + } + bool bfs(){ + for(int i=1;i<=vtot;i++){ + dis[i]=0; + cur[i] = head[i]; + } + queueq; + q.push(s);dis[s]=1; + while(!q.empty()){ + int u = q.front();q.pop(); + for(int i=head[u];~i;i = e[i].nxt){ + if(e[i].f&&!dis[e[i].v]){ + int v = e[i].v; + dis[v] = dis[u]+1; + if(v==t) return true; + q.push(v); + } + } + } + return false; + } + T dfs(int u,T m){ + if(u==t) return m; + ll flow = 0; + for(int i=cur[u];~i;cur[u]=i=e[i].nxt)//当前弧优化 + if(e[i].f&&dis[e[i].v]==dis[u]+1){ + T f = dfs(e[i].v,min(m,e[i].f)); + e[i].f-=f; + e[i^1].f+=f; + m-=f; + flow+=f; + if(!m) break; + } + if(!flow) dis[u] = -1; + return flow; + } + T dinic(){ + T flow = 0; + while(bfs()) flow+=dfs(s,numeric_limits::max()); + return flow; + } + void init(int s_,int t_,int vtot_){ + s = s_; + t = t_; + vtot = vtot_; + etot = 0; + for(int i=1;i<=vtot;i++){ + head[i]=-1; + } + } +}; +FlowGraphg; +int n,m,s,t;//s 源点 t 汇点 +void solve() +{ cin>>n>>m>>s>>t; + g.init(s,t,n); + for(int i=1;i<=m;i++){ + int u,v,w; + cin>>u>>v>>w; + g.addedge(u,v,w); + } + cout< +struct MinCostGraph{ + int s,t,vtot; + int head[V],etot; + T dis[V],flow,cost; + int pre[V]; + bool vis[V]; + + struct edge{ + int v,nxt; + T f,c; + }e[E*2]; + void addedge(int u,int v,T f,T c,T f2=0){ + e[etot] = {v,head[u],f,c};head[u] = etot++; + e[etot] = {u,head[v],f2,-c};head[v] = etot++; + } + bool spfa(){ + T inf = numeric_limits::max()/2; + for(int i=1;i<=vtot;i++){ + dis[i]=inf; + vis[i] = false; + pre[i] = -1; + } + dis[s] = 0; + vis[s] = true; + queueq; + q.push(s); + while(!q.empty()){ + int u = q.front(); + for(int i=head[u];~i;i = e[i].nxt){ + int v = e[i].v; + if(e[i].f&&dis[v]>dis[u]+e[i].c){ + dis[v] = dis[u]+e[i].c; + pre[v] = i; + if(!vis[v]){ + vis[v]=1; + q.push(v); + } + } + } + q.pop(); + vis[u] = false; + } + return dis[t]!=inf; + } + void augment(){ + int u = t; + T f = numeric_limits::max(); + while(~pre[u]){ + f = min(f,e[pre[u]].f); + u = e[pre[u]^1].v; + } + flow+=f; + cost+=f*dis[t]; + u = t; + while(~pre[u]){ + e[pre[u]].f-=f; + e[pre[u]^1].f+=f; + u = e[pre[u]^1].v; + } + } + pair solve(){ + flow=0;cost=0; + while(spfa()) augment(); + return {flow,cost}; + } + void init(int s_,int t_,int vtot_){ + s = s_; + t = t_; + vtot = vtot_; + etot = 0; + for(int i=1;i<=vtot;i++){ + head[i]=-1; + } + } +}; +MinCostGraphg; +int n,m,s,t; +void solve() +{ cin>>n>>m>>s>>t; + g.init(s,t,n); + for(int i=1;i<=m;i++){ + int u,v,w,c; + cin>>u>>v>>w>>c; + g.addedge(u,v,w,c); + } + pair ans = g.solve(); + cout<