昨晚cf打到12点半,被yy哥哥带飞,又能上分了嘿嘿
早上
昨天晚上补集训队oj上面的字典树题,wa到想死,今天早上想补网站还挂了。 。。
cf710 Div. 3 E. Restoring the Permutation
题目大意
给出一个长度为n的数组,ai表示前i个值里面的最大值,要求将数组还原成长度为n并且符合数组限制的排列,输出字典序最小的排列和最大的排列
题目思路
我们很容易就能发现当a[i]>a[i-1]时,i位置放的一定是a[i]。
根据这个结论我们可以确定一部分位置的值,这些位置的值也一定是单增的。
如果a[i]和a[j]都是能够确定的,并且i<j,i到j中间没有能够确定的值,那么i到j中所有值都应该是小于a[i]的。
对于字典序最小的情况,我们把未放置的数字从小到大放里面放,一定能够满足字典序最小和上面的要求。
对于字典序最大的情况,我们为了满足上述条件,需要每次经过一个确定的值a[i]时,把未放置并且小于a[i]的放进数组。
我们可以考虑用优先队列加双指针来模拟实现。
我们先假设i指针遍历数组查询第i个位置是否放了数,j指针遍历标记数组查询j是否放进了数组。
j指针初始化为1
首先我们先遍历1到n,如果当前位置放了a[x],那么我们移动j指针向后遍历到a[x],将未放置的数放进优先队列。当当前位置为空时,我们优先放置队列首部。这样操作下来一定是能够满足字典序最大的,并且复杂度为o(n+n)而不是纯暴力的o(n*n)
csustoj P4001 你真的会加法吗?
https://blog.csdn.net/daydreamer23333/article/details/118755890
这题是去年暑训的第一场选拔赛的题。但是字典树学完一知半解,看到这题字典树的想法都不知道。后来学长说是字典树,就想着等我重新学的时候去重写下。昨天晚上调了一晚上还是wa,重新看了学长的标称,研究了很久,还对拍搞了几组数据才过。这题一开始相当做数字做,然后很多地方不好处理,标程里面是直接当作字符串做。重写了一边发现确实好写多了。
下午
字典树就学到这把,开始重新学网络流了,去年学完又忘完了,中间训练的时候还碰到过几个网络流题目完全不会哈哈哈。
源点:网络流的起点,流量无限
汇点:网络流的终点
增广路:是从源点到汇点的一条路径,其上所有边的残余容量均大于0,流量等于增广路上所有边的流量最小值
反向边:流向相反的边,初值位0,正边减少值时,反向边增加相同大小的值,提供撤销功能
Ford-Fulkerson算法:用dfs找出存在的增广路,并维护答案
Edmond-Karp算法:用bfs找出存在的增广路,并维护答案
多路增广:即dfs找到一个增广路时向上回溯,当当前点还有边的容量不为0并且流量还有剩余就继续向下递归
当前弧优化:我们跑到一个节点时,dfs完他的一条边,遍历他的下一条边时,这条边的容量已经为空,我们之后都不能走这条边了。所以为了防止搜索时间浪费在这些容量为空的边上,我们每次跑到一条新边时,把链式前向行的head数组的对应起点的值更新为当前边
Dinic算法:上面两种算法的优化版,找增广路的方式是bfs和dfs结合。先用bfs对现存的图分层,类似树形结构里面求深度的方法(分层的好处是我们每次想下个点跑时只往深度小于自己的点跑,防止绕圈或者绕远路),然后dfs找增广路。如果分层时无法走到汇点说明没有增广路了,此时的答案就是最大流
模板:
int n, m, s, t, lv[MAXN], cur[MAXN]; // lv是每个点的层数,cur用于当前弧优化标记增广起点
inline bool bfs() // BFS分层
{
memset(lv, -1, sizeof(lv));
lv[s] = 0;
memcpy(cur, head, sizeof(head)); // 当前弧优化初始化
queue<int> q;
q.push(s);
while (!q.empty())
{
int p = q.front();
q.pop();
for (int eg = head[p]; eg; eg = edges[eg].next)
{
int to = edges[eg].to, vol = edges[eg].w;
if (vol > 0 && lv[to] == -1)
lv[to] = lv[p] + 1, q.push(to);
}
}
return lv[t] != -1; // 如果汇点未访问过说明已经无法达到汇点,此时返回false
}
int dfs(int p = s, int flow = INF)
{
if (p == t)
return flow;
int rmn = flow; // 剩余的流量
for (int eg = cur[p]; eg && rmn; eg = edges[eg].next) // 如果已经没有剩余流量则退出
{
cur[p] = eg; // 当前弧优化,更新当前弧
int to = edges[eg].to, vol = edges[eg].w;
if (vol > 0 && lv[to] == lv[p] + 1) // 往层数高的方向增广
{
int c = dfs(to, min(vol, rmn)); // 尽可能多地传递流量
rmn -= c; // 剩余流量减少
edges[eg].w -= c; // 更新残余容量
edges[eg ^ 1].w += c; // 再次提醒,链式前向星的cnt需要初始化为1(或-1)才能这样求反向边
}
}
return flow - rmn; // 返回传递出去的流量的大小
}
inline int dinic()
{
int ans = 0;
while (bfs())
ans += dfs();
return ans;
}
割:从网络中选择一些边,使得去掉这些边后,剩下两个不连通的分别包含源点和汇点的点集。割的大小是这些边的容量之和。在所有可行的割中,最小的割称为最小割。
最大流最小割定理:最大流等于最小割
证明博客链接:https://blog.csdn.net/jy02660221/article/details/83471968(证明看的大概能看懂,但是看完有感觉有点不明白)
洛谷P3376 【模板】网络最大流
代码
#pragma comment(linker, "/STACK:102400000,102400000")
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <utility>
#define pi 3.1415926535898
#define ll long long
#define lson rt<<1
#define rson rt<<1|1h
#define eps 1e-6
#define ms(a,b) memset(a,b,sizeof(a))
#define legal(a,b) a&b
#define print1 printf("111\n")
#define pb(x) push_back(x)
using namespace std;
const int maxn = 2e6+10;
const int inf = 1e9;
const ll llinf =1e18+10;
const ll mod = 1e9+7;
int cnt=1;
struct node
{
int to,next;
ll w;
}e[maxn];
int first[maxn];
int n,m,s,t;
int dep[maxn];
int cur[maxn];
void add(int u,int v,ll w)
{
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].next=first[u];
first[u]=cnt;
}
inline bool bfs()
{
ms(dep,-1);
dep[s]=0;
memcpy(cur,first,sizeof(first));
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=first[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
ll vol=e[i].w;
if(vol>0&&dep[to]==-1)
{
dep[to]=dep[u]+1;
q.push(to);
}
}
}
return dep[t]!=-1;
}
inline ll dfs(int u=s,ll flow=llinf)
{
if(u==t)
return flow;
ll last=flow;
for(int i=cur[u];i!=-1&&last;i=e[i].next)
{
cur[u]=i;
int to=e[i].to;
ll vol=e[i].w;
//if(last==0)break;
//如果把last的判断条件从for里面拿到上面这里就会t,不动就过了,感觉很神奇
if(vol>0&&dep[to]==dep[u]+1)
{
int c=dfs(to,min(vol,last));
last-=c;
e[i].w-=c;
e[i^1].w+=c;
}
}
return flow-last;
}
inline ll dinic()
{
ll ans=0;
while(bfs())
{
ans+=dfs();
}
return ans;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
ms(first,-1);
for(int i=1;i<=m;i++)
{
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);
add(v,u,0);
}
printf("%lld\n",dinic());
}
晚上
看了下网络流好多各种各样的概念,有点头疼。
准备学完之后全部搬到一个博客里去
晚训三道题过了两道。
第一道简单签到题,
第二道简单贪心。
第三道题是线段树,看了很久还是不知道咋做,再次面向题解编程了。
感觉现在做线段树的题,脑子里面还是只有维护最大值、最小值、区间和,题目也不去分析,就一直硬想,有点自闭。