2017 Multi-University Training Contest - Team 9 待补 hdu 6161 ~6170
2017-08-23 15:36
495 查看
hdu6161 Big binary tree
题意:
给定一棵完全二叉树,每次询问给出一个节点的编号,问所有经过这个点的路径的最大值。路径的值被定义为
路径上所有节点的数值之和。
思路:
有趣的dp.
dp[i] 表示的是 以i结点为根结点往下走获得的路径最大和.
则有: dp[i] = max(dp[i << 1],dp[i << 1 | 1]) + num[i] (num[i] 为结点i的值);
到底往哪个孩子走才能最大呢?
因为是一个完全二叉树,我们dp处理的是从i节点往下走的路径最大和,所以如果每次更改节点值时只会影响他上面
的结点的dp值.所以没更改一个值时我们就需要向上去维护.
对于一个没有更改过的子树,如果左右子树深度相同,根据编号的性质,肯定往右走使得值更大,如果深度不相同,根据
完全二叉树性质肯定左子树深度大,那么就往左走.
如果要查询经过i结点的path的最大值,我们就从i结点向上查询即可.但是发现这条路径可能有两种:
1. 一种是从i结点的 左孩子上来,经过i,在走i结点的右孩子的路径.
2. 一种是从i结点向上,(i结点下面的路径最大值dp统计过了)走到某一个点,在沿着他的另一个孩子去走下去的
路径最大值. (这两种情况只要你画个图就很明显了)
那么我们就维护这两种情况即可.
但是我们发现n有点大啊,好像怎么存都存不下,因为那些没有更改过值得点的值我们是知道的,所以我们只需要开一
哥map 记录哪一个点的值更改过了,那些没有更改过值得子树,他往下走的最大路径和我们也是可以计算出来的,这个我
们也可以不用记录,只需要开一个dp,记录更改过值的子树的往下走的最大的路径和.然后每次查询维护上面你的那个最大
值就好了.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
map<ll,ll>dp,val;
ll n,m;
char s[20];
int deep(int x)//求深度
{
int dep = 0;
while(x <= n)
{
x <<= 1;
dep++;
}
return dep;
}
ll down(int x)//每个节点向下走的最大路径和
{
if(x > n)
return 0;
if(dp.count(x)) //如果更改过直接返回.
return dp[x];
ll res = (val.count(x) ? val[x] : x);
if(deep(x) == 1)
return res;
if(deep(x << 1) == deep(x << 1 | 1))
res += down(x << 1 | 1);
else
res += down(x << 1);
return res;
}
void change(ll x,ll num)
{
val[x] = num;
dp[x] = max(down(x << 1),down(x << 1 | 1)) + val[x];
while(x > 1)//每次改变一个结点,只会影响他上面那些点
{
x >>= 1;
dp[x] = max(down(x << 1),down(x << 1 | 1)) + (val.count(x)?val[x]:x);
}
return ;
}
ll query(ll x)
{
ll ans = down(x << 1) + down(x << 1 | 1) + (val.count(x) ? val[x] : x);
//该点的左孩子结点的最大 + 自己 +右孩子结点的最大
int prex;
ll res = down(x);
while(x > 1)
{
prex = x;
x >>= 1;
res += (val.count(x) ? val[x] : x);//从该结点向上走,再走他另一个子树.
ll tmp;
if(prex % 2 == 1)
tmp = res + down(x << 1);
else
tmp = res + down(x << 1 | 1);
ans = max(tmp,ans);
}
return ans;
}
int main()
{
while(~scanf("%lld %lld",&n,&m))
{
dp.clear();
val.clear();
for(int i = 1; i <= m;i++)
{
scanf("%s",s);
ll x,y;
if(s[0] == 'q')
{
scanf(" %lld",&x);
printf("%lld\n",query(x));
}
else
{
scanf(" %lld %lld",&x,&y);
change(x,y);
}
}
}
return 0;
}
hdu6162 Ch’s gift
题意:
有一棵树,n个节点n-1条边,每个节点都有一个权值。然后m次查询输入x,y,a,b意思是从节点到x,y这条路径
上的所有权值在区间[a,b]内的权值和。
思路:
给你一棵树,还要问你树上的路径 x->y 这样的,一般就可以树剖的.
这个题目难点在于怎么处理区间[a,b]。将树进行树链剖分然后映射到线段树上,因为线段树上就可以进行区间操作,我们开几个数组mi[] 表示对应区间的最小值,mx[]表示对应区间的最大值,sum[]表示对应区间的权值和.当查询到这个区间的时候 如果该区间最小值mi>b || 最大值mx < a 则这些都不是答案, 如果最小值和最大值 范围在[a,b]内则这个区间是答案的一部分,然后累加.
这个题目如果来一个处理一个肯定T了,我们进行离线处理,建一颗线段树处理出所有的 情况.
复杂度 O((n+q)*logn)
PS :
还有一个比较好的做法是,也是先将树进行剖分,然后映射到线段树上,然后我们每次先统计 权值范围在(1,a-1)的和s1,在1~b的和s2. S2-s1即可得到答案.
这种做法是先将所有的点的权值从小到大排序,然后对m个操作按a升序排一次,如果该点的权值小于a,就把其在线段树对应的点进行单点更新,更新为它的权值,然后对对应的x,y求一次路径,即可统计出该路径上点的权值在(1,a-1)的和,
同样的,也按b排序求一次.
hdu6165 FFF at Valentine
题意:
种种原因...签到题这次都没出,先是各种猜题意,然后题意对了,图论小白并不会做,队友卡在02主席树....
给你一个无环,无重边的有向图,问你任意两点,是否存在路径使得其中一点能到达另一点
思路:
一开始想的是强联通分量缩点,缩点后剩下的点必须构成一条链,于是通过判断每个点的出入度不能大于等于2..
随后发现了这个样例: (懒得作图 ,dao tu)
就是很特殊 不是一条链,但是并没有成环.这样的当时想了一会并不会判断.。。。。现在想想只要缩点之后跑一遍拓扑排序,要求每一层的拓扑排序只有一个入度为0的点即可.
tarjan 复杂度O(n+m)。拓扑排序复杂度最坏也是O(n+m).
PS : 缩点之后直接爆搜也可以过了, 复杂度是O(N*M)。
hdu6166 Senior Pan
题意:
给你一个有向图,然后给你k个点,求其中一个点到另一个点的距离的最小值。
思路:
非常好的一个思维 + 图论的题目.你只要想到了就可以以很低的复杂度做出来,想不到...那这个题目就别想了.
首先想想图论的最短路的三个算法,这个题貌似是多源最短路,但是Floyd N^3 铁定爆炸了,那就剩下dij和spfa了.
其实这两个在这个题目都可以,这里我们用堆优化的dij吧。 考虑到dij可以求两个集合间的最短路径,(即初始将某个集合所有点入堆,dis = 0,直到找到另一个集合的第一个点结束)。
想到了这个问题我们就来想一下怎么去划分集合了,因为任意两点都要求一次,所以划分集合的方法必须要保证每两个点都至少一次被分到了不同的集合里去.
这里比较巧妙的一个方法就是 因为每个点都不相同,所以他们点的二进制状态中至少有一位不相同,那么我们就可以枚举二进制中的每一位,相同的分到一个集合,然后跑一次dij维护最小值即可.因为n为1e5,所以最多17次就可以结束了.
另外一个需要注意的问题就是,该图是一个有向图,所以划分完集合后我们不确定是1集合到2集合最短,还是2集合到1集合最短,所以我们要正反跑两次dij,一次1到2,一次2到1.维护一个最小值,就是答案了.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
typedef long long ll;
const ll inf = 0x3f3f3f3f;
int n,m,k;
int book[maxn],vis[maxn],num[maxn];
ll dis[maxn],ans;
struct Edge
{
int to,w,nxt;
}edge[maxn];
struct node
{
int x;
ll s;
bool operator< (const node &w) const
{
return s > w.s;
}
};
int tot,head[maxn];
priority_queue<node>Q;
void init()
{
memset(book,0,sizeof book);
memset(vis,0,sizeof vis);
for(int i = 0;i <= n;i++)
dis[i] = inf;
while(!Q.empty())
Q.pop();
}
void add(int u,int v,int w)
{
edge[tot].to = v;
edge[tot].w=w;
edge[tot].nxt = head[u];
head[u] = tot++;
}
ll dij()
{
while(!Q.empty())
{
node now = Q.top();
Q.pop();
int u = now.x;
if(book[u] == 2)
return now.s;
if(vis[u]) continue;
for(int i =head[u];i != -1;i = edge[i].nxt)
{
int v = edge[i].to;
int w= edge[i].w;
if(!vis[u] && dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
Q.push(node{v,dis[v]});
}
}
}
return inf;
}
void solve()
{
ans = inf;
for(int i = 0;i < 18;i++)
{
init();
for(int j = 1;j <= k;j++)
{
if(num[j] & (1 << i))
{
dis[num[j]] = 0;
book[num[j]] = 1;
Q.push({num[j],0});
}
else
book[num[j]] = 2;
}
ans = min(ans,dij());
init();
for(int j = 1;j <= k;j++)
{
if(num[j] & (1 << i))
book[num[j]] = 2;
else
{
dis[num[j]] = 0;
book[num[j]] = 1;
Q.push(node{num[j],0});
}
}
ans = min(ans,dij());
}
}
int main()
{
int _;
cin>>_;
int t = 0;
while(_--)
{
memset(head,-1,sizeof head);
tot = 0;
scanf("%d %d",&n,&m);
for(int i = 1;i <= m;i++)
{
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
add(u,v,w);
}
scanf("%d",&k);
for(int i = 1;i <= k;i++)
scanf("%d",&num[i]);
solve();
printf("Case #%d: %lld\n",++t,ans);
}
return 0;
}
hdu 6168
题意:
有一个长度为n的序列a1……an,根据a序列生成了一个b序列,b[i] = a[i]+aj,然后有一个人把a,b序列按随机顺序混合了起来,现在问你初始的a序列是什么
思路:
首先对整个序列排序,然后你会发现最小的两个一定是属于a的,然后这两个的和可以删掉后面的一个...这时候除了这两个元素最小的也一定是a的,再加进来,删除能组成的,以此类推..选出n个
hdu6170 Two string
题意:
给出一个原串和一个需要匹配的串,当然匹配串有两种操作。
‘.’ 可以变成任何字母 ∗ 可以使得前一个字母变成任何长度>= 0,例如:
a∗,可以变成“aa”,“aaa”甚至把a消失“”
问是否能从匹配串变成原串。
思路:
首先转一个大佬的思路,什么正则表达式....
和标准正则表达式不同的是".*"模式串在题意下不能匹配"abcde"这样的字符串
按题意".*"的意思是相同字符的0个或多个重复串
那么我们把模式串中的".*"替换成"(.)\1*"即可
来说说一般做法,dp.
dp[i][j]表示 b[1~i] 和a[1~j] 是否完成了匹配.
那么有以下几种情况:
1.b[i] == a[j] || b[i] == '.' 则dp[i][j] = dp[i-1][j-1]
2. 比较不好考虑的就是 b[i] == '*' ,怎么去处理他匹配0个 ....多个,还有把前面的字符删除.
当dp[i-2][j] == 1 表示 *和前面的那个字符可以删除,不匹配 .则 dp[i][j] = 1;
当dp[i-1][j] == 1 表示*前面的那个字符和该字符匹配,*可以不匹配.则 dp[i][j]= 1;
当dp[i-1][j-1] 说明此时要想继续往下匹配,*必须和该字符匹配le 。 再判断一下 a[j] ==a[j-1]?
当dp[i][j - 1] 说明*已经和j前面的字符匹配过了,这时候他要匹配多个字符了/
题意:
给定一棵完全二叉树,每次询问给出一个节点的编号,问所有经过这个点的路径的最大值。路径的值被定义为
路径上所有节点的数值之和。
思路:
有趣的dp.
dp[i] 表示的是 以i结点为根结点往下走获得的路径最大和.
则有: dp[i] = max(dp[i << 1],dp[i << 1 | 1]) + num[i] (num[i] 为结点i的值);
到底往哪个孩子走才能最大呢?
因为是一个完全二叉树,我们dp处理的是从i节点往下走的路径最大和,所以如果每次更改节点值时只会影响他上面
的结点的dp值.所以没更改一个值时我们就需要向上去维护.
对于一个没有更改过的子树,如果左右子树深度相同,根据编号的性质,肯定往右走使得值更大,如果深度不相同,根据
完全二叉树性质肯定左子树深度大,那么就往左走.
如果要查询经过i结点的path的最大值,我们就从i结点向上查询即可.但是发现这条路径可能有两种:
1. 一种是从i结点的 左孩子上来,经过i,在走i结点的右孩子的路径.
2. 一种是从i结点向上,(i结点下面的路径最大值dp统计过了)走到某一个点,在沿着他的另一个孩子去走下去的
路径最大值. (这两种情况只要你画个图就很明显了)
那么我们就维护这两种情况即可.
但是我们发现n有点大啊,好像怎么存都存不下,因为那些没有更改过值得点的值我们是知道的,所以我们只需要开一
哥map 记录哪一个点的值更改过了,那些没有更改过值得子树,他往下走的最大路径和我们也是可以计算出来的,这个我
们也可以不用记录,只需要开一个dp,记录更改过值的子树的往下走的最大的路径和.然后每次查询维护上面你的那个最大
值就好了.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
map<ll,ll>dp,val;
ll n,m;
char s[20];
int deep(int x)//求深度
{
int dep = 0;
while(x <= n)
{
x <<= 1;
dep++;
}
return dep;
}
ll down(int x)//每个节点向下走的最大路径和
{
if(x > n)
return 0;
if(dp.count(x)) //如果更改过直接返回.
return dp[x];
ll res = (val.count(x) ? val[x] : x);
if(deep(x) == 1)
return res;
if(deep(x << 1) == deep(x << 1 | 1))
res += down(x << 1 | 1);
else
res += down(x << 1);
return res;
}
void change(ll x,ll num)
{
val[x] = num;
dp[x] = max(down(x << 1),down(x << 1 | 1)) + val[x];
while(x > 1)//每次改变一个结点,只会影响他上面那些点
{
x >>= 1;
dp[x] = max(down(x << 1),down(x << 1 | 1)) + (val.count(x)?val[x]:x);
}
return ;
}
ll query(ll x)
{
ll ans = down(x << 1) + down(x << 1 | 1) + (val.count(x) ? val[x] : x);
//该点的左孩子结点的最大 + 自己 +右孩子结点的最大
int prex;
ll res = down(x);
while(x > 1)
{
prex = x;
x >>= 1;
res += (val.count(x) ? val[x] : x);//从该结点向上走,再走他另一个子树.
ll tmp;
if(prex % 2 == 1)
tmp = res + down(x << 1);
else
tmp = res + down(x << 1 | 1);
ans = max(tmp,ans);
}
return ans;
}
int main()
{
while(~scanf("%lld %lld",&n,&m))
{
dp.clear();
val.clear();
for(int i = 1; i <= m;i++)
{
scanf("%s",s);
ll x,y;
if(s[0] == 'q')
{
scanf(" %lld",&x);
printf("%lld\n",query(x));
}
else
{
scanf(" %lld %lld",&x,&y);
change(x,y);
}
}
}
return 0;
}
hdu6162 Ch’s gift
题意:
有一棵树,n个节点n-1条边,每个节点都有一个权值。然后m次查询输入x,y,a,b意思是从节点到x,y这条路径
上的所有权值在区间[a,b]内的权值和。
思路:
给你一棵树,还要问你树上的路径 x->y 这样的,一般就可以树剖的.
这个题目难点在于怎么处理区间[a,b]。将树进行树链剖分然后映射到线段树上,因为线段树上就可以进行区间操作,我们开几个数组mi[] 表示对应区间的最小值,mx[]表示对应区间的最大值,sum[]表示对应区间的权值和.当查询到这个区间的时候 如果该区间最小值mi>b || 最大值mx < a 则这些都不是答案, 如果最小值和最大值 范围在[a,b]内则这个区间是答案的一部分,然后累加.
这个题目如果来一个处理一个肯定T了,我们进行离线处理,建一颗线段树处理出所有的 情况.
复杂度 O((n+q)*logn)
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; struct node { int x,y; ll a,b; }q[maxn]; ll mi[maxn<<2],mx[maxn<<2],sum[maxn<<2]; int n,m,tot; ll c[maxn],val[maxn]; int son[maxn],top[maxn],id[maxn],num[maxn]; int deep[maxn],fa[maxn]; vector<int>vt[maxn]; void init() { tot = 0; for(int i = 1;i <= n;i++) vt[i].clear(),son[i] = 0; } void dfs1(int x,int f,int d) { deep[x] = d; fa[x] = f; num[x] = 1; int len = vt[x].size(); for(int i = 0;i < len;i++) { int v = vt[x][i]; if(v == f) continue; dfs1(v,x,d+1); num[x] += num[v]; if(!son[x] || num[v] > num[son[x]]) son[x] = v; } return ; } void dfs2(int x,int number) { top[x] = number; id[x] = ++tot; if(!son[x]) return ; dfs2(son[x],number); int len = vt[x].size(); for(int i = 0;i < len;i++) { int v = vt[x][i]; if(v != son[x] &&v != fa[x]) dfs2(v,v); } return ; } void push_up(int d) { mi[d] = min(mi[d << 1],mi[d << 1 | 1]); mx[d] = max(mx[d << 1],mx[d << 1 | 1]); sum[d] = sum[d << 1] + sum[d << 1 | 1]; return ; } void build(int l,int r,int d) { if(l == r) { mi[d] = mx[d] = sum[d] = val[l]; return ; } int mid = (l + r) >> 1; build(l,mid,d << 1); build(mid + 1,r,d << 1 | 1); push_up(d); return ; } ll query(int l,int r,int L,int R,int d,ll a,ll b) { if(L <= l &&r <= R) { if(mi[d] > b || mx[d] < a) return 0; if(mi[d] >= a && mx[d] <= b) return sum[d]; } int mid = (l + r) >> 1; ll res = 0; if(L <= mid) res += query(l,mid,L,R,d << 1,a,b); if(R > mid) res += query(mid + 1,r,L,R,d << 1 | 1 ,a,b); return res; } void sp_tree() { dfs1(1,0,1); dfs2(1,1); for(int i = 1;i <= n;i++) val[id[i]] = c[i]; } ll ask_sum(int x,int y,ll a,ll b) { int f1 = top[x],f2 = top[y]; ll res = 0; while(f1 != f2) { if(deep[f1] < deep[f2]) swap(f1,f2),swap(x,y); res += query(1,n,id[f1],id[x],1,a,b); x = fa[f1],f1 = top[x]; } res += deep[x] > deep[y]?query(1,n,id[y],id[x],1,a,b) : query(1,n,id[x],id[y],1,a,b); return res; } void solve() { build(1,n,1); for(int i = 1;i <= m;i++) { printf("%lld",ask_sum(q[i].x,q[i].y,q[i].a,q[i].b)); printf("%c",i == m ? '\n' : ' '); } return ; } int main() { while(~scanf("%d%d",&n,&m)) { init(); for(int i = 1;i <= n;i++) { scanf("%lld",&c[i]); } for(int i = 1;i < n;i++) { int u,v; scanf("%d %d",&u,&v); vt[u].push_back(v); vt[v].push_back(u); } for(int i = 1;i <= m;i++) { scanf("%d %d %lld %lld",&q[i].x,&q[i].y,&q[i].a,&q[i].b); } sp_tree(); solve(); } return 0; }
PS :
还有一个比较好的做法是,也是先将树进行剖分,然后映射到线段树上,然后我们每次先统计 权值范围在(1,a-1)的和s1,在1~b的和s2. S2-s1即可得到答案.
这种做法是先将所有的点的权值从小到大排序,然后对m个操作按a升序排一次,如果该点的权值小于a,就把其在线段树对应的点进行单点更新,更新为它的权值,然后对对应的x,y求一次路径,即可统计出该路径上点的权值在(1,a-1)的和,
同样的,也按b排序求一次.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5+5; int deep[maxn], fa[maxn], top[maxn], tree[maxn], pre[maxn], son[maxn], num[maxn]; int n, q, tot; ll treeSum[maxn*4], ansL[maxn], ansR[maxn]; vector<int> g[maxn]; struct node1 { int x, id; bool operator < (const node1 &a) const { return x < a.x; } }a[maxn]; struct node2 { int x, y, a, b, id; }op[maxn]; bool cmp1(const node2 &a, const node2 &b) { return a.a < b.a; } bool cmp2(const node2 &a, const node2 &b) { return a.b < b.b; } void init() { tot = 0; memset(son, 0, sizeof(son)); memset(num, 0, sizeof(num)); for(int i = 0; i <= n; i++) g[i].clear(); } void dfs1(int u, int pre, int d) { deep[u] = d; fa[u] = pre; num[u] = 1; for(int i = 0, len = g[u].size(); i < len; i++) { int v = g[u][i]; if(v == pre) continue; dfs1(v, u, d+1); num[u] += num[v]; if(!son[v] || num[v] > num[son[u]]) son[u] = v; } } void dfs2(int u, int tp) { top[u] = tp; tree[u] = ++tot; pre[tree[u]] = u; if(!son[u]) return ; dfs2(son[u], tp); for(int i = 0, len = g[u].size(); i < len; i++) { int v = g[u][i]; if(v != son[u] && v != fa[u]) dfs2(v, v); } } void push_up(int root) { treeSum[root] = treeSum[root*2]+treeSum[root*2+1]; } void update(int root, int l, int r, int pos, int val) { if(l == r) { treeSum[root] += val; return ; } int mid = (l+r)/2; if(pos <= mid) update(root*2, l, mid, pos, val); else update(root*2+1, mid+1, r, pos, val); push_up(root); } ll query(int root, int l, int r, int i, int j) { if(i <= l && j >= r) return treeSum[root]; int mid = (l+r)/2; ll res = 0; if(i <= mid) res += query(root*2, l, mid, i, j); if(j > mid) res += query(root*2+1, mid+1, r, i, j); return res; } ll ask(int x, int y) { int f1 = top[x], f2 = top[y]; ll ans = 0; while(f1 != f2) { if(deep[f1] < deep[f2]) swap(f1, f2), swap(x, y); ans += query(1, 1, n, tree[f1], tree[x]); x = fa[f1], f1 = top[x]; } ans += (deep[x]>deep[y]) ? query(1, 1, n, tree[y], tree[x]) : query(1, 1, n, tree[x], tree[y]); return ans; } int main(void) { while(cin >> n >> q) { init(); for(int i = 1; i <= n; i++) scanf("%d", &a[i].x), a[i].id = i; for(int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } dfs1(1, 0, 1); dfs2(1, 1); for(int i = 1; i <= q; i++) scanf("%d%d%d%d", &op[i].x, &op[i].y, &op[i].a, &op[i].b), op[i].id = i; sort(a+1, a+1+n); //L memset(treeSum, 0, sizeof(treeSum)); sort(op+1, op+1+q, cmp1); for(int i = 1, j = 1; i <= q; i++) { while(j <= n && a[j].x < op[i].a) { update(1, 1, n, tree[a[j].id], a[j].x); j++; } ansL[op[i].id] = ask(op[i].x, op[i].y); } //R memset(treeSum, 0, sizeof(treeSum)); sort(op+1, op+1+q, cmp2); for(int i = 1, j = 1; i <= q; i++) { while(j <= n && a[j].x <= op[i].b) { update(1, 1, n, tree[a[j].id], a[j].x); j++; } ansR[op[i].id] = ask(op[i].x, op[i].y); } for(int i = 1; i <= q; i++) { if(i != 1) printf(" "); printf("%lld", ansR[i]-ansL[i]); } puts(""); } return 0; }
hdu6165 FFF at Valentine
题意:
种种原因...签到题这次都没出,先是各种猜题意,然后题意对了,图论小白并不会做,队友卡在02主席树....
给你一个无环,无重边的有向图,问你任意两点,是否存在路径使得其中一点能到达另一点
思路:
一开始想的是强联通分量缩点,缩点后剩下的点必须构成一条链,于是通过判断每个点的出入度不能大于等于2..
随后发现了这个样例: (懒得作图 ,dao tu)
就是很特殊 不是一条链,但是并没有成环.这样的当时想了一会并不会判断.。。。。现在想想只要缩点之后跑一遍拓扑排序,要求每一层的拓扑排序只有一个入度为0的点即可.
tarjan 复杂度O(n+m)。拓扑排序复杂度最坏也是O(n+m).
PS : 缩点之后直接爆搜也可以过了, 复杂度是O(N*M)。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e3 + 10; vector<int>G[maxn],g[maxn]; stack<int>st; int dfn[maxn],low[maxn],belong[maxn],in[maxn]; int dfs_clock,scc_cnt; //belong 存的就是给每一块强联通分量编号. int n,m; void init() { memset(dfn,0,sizeof dfn); memset(low,0,sizeof low); memset(belong,0,sizeof belong); memset(in,0,sizeof in); for(int i = 0;i <= n;i++) { G[i].clear(); g[i].clear(); } //while(!st.empty()) //st.pop(); dfs_clock = scc_cnt = 0; return ; } void dfs(int u) { dfn[u] = low[u] = ++dfs_clock; st.push(u); int len = G[u].size(); for(int i = 0;i < len;i++) { int v = G[u][i]; if(!dfn[v]) { dfs(v); low[u] = min(low[u],low[v]); } else if(!belong[v]) low[u] = min(low[u],dfn[v]); } if(low[u] == dfn[u]) { scc_cnt++; while(1) { int x = st.top(); st.pop(); belong[x] = scc_cnt; if(x == u) break; } } return ; } void solve() { for(int i = 1;i <= n;i++) { int len = G[i].size(); for(int j = 0; j < len;j++) { int v = G[i][j]; if(belong[i] != belong[v]) { g[belong[i]].push_back(belong[v]); in[belong[v]]++; } } } int cnt = 0; queue<int>Q; for(int i = 1;i <= scc_cnt;i++) { if(in[i] == 0) { Q.push(i); cnt ++; } } if(cnt == 1) { int flag = 0; while(!Q.empty()) { cnt = 0; int u = Q.front(); Q.pop(); int len = g[u].size(); for(int i = 0;i < len;i++) { int v = g[u][i]; in[v]--; if(in[v] == 0) { cnt++; Q.push(v); } } if(cnt > 1) { flag = 1; break; } } if(flag) puts("Light my fire!"); else puts("I love you my love and our love save us!"); } else { puts("Light my fire!"); } return ; } int main() { int _; cin>>_; while(_--) { scanf("%d %d",&n,&m); init(); for(int i = 1;i <= m;i++) { int u,v; scanf("%d %d",&u,&v); G[u].push_back(v); } for(int i = 1;i <= n;i++) { if(!dfn[i]) dfs(i); } solve(); } }
hdu6166 Senior Pan
题意:
给你一个有向图,然后给你k个点,求其中一个点到另一个点的距离的最小值。
思路:
非常好的一个思维 + 图论的题目.你只要想到了就可以以很低的复杂度做出来,想不到...那这个题目就别想了.
首先想想图论的最短路的三个算法,这个题貌似是多源最短路,但是Floyd N^3 铁定爆炸了,那就剩下dij和spfa了.
其实这两个在这个题目都可以,这里我们用堆优化的dij吧。 考虑到dij可以求两个集合间的最短路径,(即初始将某个集合所有点入堆,dis = 0,直到找到另一个集合的第一个点结束)。
想到了这个问题我们就来想一下怎么去划分集合了,因为任意两点都要求一次,所以划分集合的方法必须要保证每两个点都至少一次被分到了不同的集合里去.
这里比较巧妙的一个方法就是 因为每个点都不相同,所以他们点的二进制状态中至少有一位不相同,那么我们就可以枚举二进制中的每一位,相同的分到一个集合,然后跑一次dij维护最小值即可.因为n为1e5,所以最多17次就可以结束了.
另外一个需要注意的问题就是,该图是一个有向图,所以划分完集合后我们不确定是1集合到2集合最短,还是2集合到1集合最短,所以我们要正反跑两次dij,一次1到2,一次2到1.维护一个最小值,就是答案了.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
typedef long long ll;
const ll inf = 0x3f3f3f3f;
int n,m,k;
int book[maxn],vis[maxn],num[maxn];
ll dis[maxn],ans;
struct Edge
{
int to,w,nxt;
}edge[maxn];
struct node
{
int x;
ll s;
bool operator< (const node &w) const
{
return s > w.s;
}
};
int tot,head[maxn];
priority_queue<node>Q;
void init()
{
memset(book,0,sizeof book);
memset(vis,0,sizeof vis);
for(int i = 0;i <= n;i++)
dis[i] = inf;
while(!Q.empty())
Q.pop();
}
void add(int u,int v,int w)
{
edge[tot].to = v;
edge[tot].w=w;
edge[tot].nxt = head[u];
head[u] = tot++;
}
ll dij()
{
while(!Q.empty())
{
node now = Q.top();
Q.pop();
int u = now.x;
if(book[u] == 2)
return now.s;
if(vis[u]) continue;
for(int i =head[u];i != -1;i = edge[i].nxt)
{
int v = edge[i].to;
int w= edge[i].w;
if(!vis[u] && dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
Q.push(node{v,dis[v]});
}
}
}
return inf;
}
void solve()
{
ans = inf;
for(int i = 0;i < 18;i++)
{
init();
for(int j = 1;j <= k;j++)
{
if(num[j] & (1 << i))
{
dis[num[j]] = 0;
book[num[j]] = 1;
Q.push({num[j],0});
}
else
book[num[j]] = 2;
}
ans = min(ans,dij());
init();
for(int j = 1;j <= k;j++)
{
if(num[j] & (1 << i))
book[num[j]] = 2;
else
{
dis[num[j]] = 0;
book[num[j]] = 1;
Q.push(node{num[j],0});
}
}
ans = min(ans,dij());
}
}
int main()
{
int _;
cin>>_;
int t = 0;
while(_--)
{
memset(head,-1,sizeof head);
tot = 0;
scanf("%d %d",&n,&m);
for(int i = 1;i <= m;i++)
{
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
add(u,v,w);
}
scanf("%d",&k);
for(int i = 1;i <= k;i++)
scanf("%d",&num[i]);
solve();
printf("Case #%d: %lld\n",++t,ans);
}
return 0;
}
hdu 6168
题意:
有一个长度为n的序列a1……an,根据a序列生成了一个b序列,b[i] = a[i]+aj,然后有一个人把a,b序列按随机顺序混合了起来,现在问你初始的a序列是什么
思路:
首先对整个序列排序,然后你会发现最小的两个一定是属于a的,然后这两个的和可以删掉后面的一个...这时候除了这两个元素最小的也一定是a的,再加进来,删除能组成的,以此类推..选出n个
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> using namespace std; typedef long long ll; const int maxn = 1e6+5; int a[maxn], ans[maxn]; int main(void) { int m; while(cin >> m) { map<int, int> mm; mm.clear(); for(int i = 1; i <= m; i++) scanf("%d", &a[i]), mm[a[i]]++; int cnt = 0; int n = 0; for(int i = 0; i < m; i++) { if(i*(i+1)%2 == 0 && i*(i+1)/2 == m) { n = i; break; } } map<int, int>::iterator it, ee; ee = mm.end(); for(it = mm.begin(); it != ee; ) { // cout << (*it).first << ' ' << (*it).second << endl; if((*it).second < 1) it++; else if((*it).second >= 1) { int x = (*it).first; mm[x]--; for(int i = 0; i < cnt; i++) mm[ans[i]+x]--; ans[cnt++] = x; } } printf("%d\n", n); for(int i = 0; i < cnt; i++) { if(i) printf(" "); printf("%d", ans[i]); } printf("\n"); } return 0; }
hdu6170 Two string
题意:
给出一个原串和一个需要匹配的串,当然匹配串有两种操作。
‘.’ 可以变成任何字母 ∗ 可以使得前一个字母变成任何长度>= 0,例如:
a∗,可以变成“aa”,“aaa”甚至把a消失“”
问是否能从匹配串变成原串。
思路:
首先转一个大佬的思路,什么正则表达式....
和标准正则表达式不同的是".*"模式串在题意下不能匹配"abcde"这样的字符串
按题意".*"的意思是相同字符的0个或多个重复串
那么我们把模式串中的".*"替换成"(.)\1*"即可
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstring> #include <string> #include <string.h> #include <map> #include <set> #include <queue> #include <deque> #include <list> #include <bitset> #include <stack> #include <stdlib.h> #include <regex> #define lowbit(x) (x&-x) #define e exp(1.0) #define eps 1e-8 //ios::sync_with_stdio(false); // auto start = clock(); // cout << (clock() - start) / (double)CLOCKS_PER_SEC; typedef long long ll; typedef long long LL; using namespace std; int main() { ios::sync_with_stdio(false); int T; cin>>T; while(T--) { string a,b; cin>>a>>b; b=regex_replace(b,regex("\\.\\*"),"(.)\\1*"); regex_match(a,regex(b))?cout<<"yes"<<endl:cout<<"no"<<endl; } return 0; }
来说说一般做法,dp.
dp[i][j]表示 b[1~i] 和a[1~j] 是否完成了匹配.
那么有以下几种情况:
1.b[i] == a[j] || b[i] == '.' 则dp[i][j] = dp[i-1][j-1]
2. 比较不好考虑的就是 b[i] == '*' ,怎么去处理他匹配0个 ....多个,还有把前面的字符删除.
当dp[i-2][j] == 1 表示 *和前面的那个字符可以删除,不匹配 .则 dp[i][j] = 1;
当dp[i-1][j] == 1 表示*前面的那个字符和该字符匹配,*可以不匹配.则 dp[i][j]= 1;
当dp[i-1][j-1] 说明此时要想继续往下匹配,*必须和该字符匹配le 。 再判断一下 a[j] ==a[j-1]?
当dp[i][j - 1] 说明*已经和j前面的字符匹配过了,这时候他要匹配多个字符了/
#include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int maxn = 3e3 + 10; int dp[maxn][maxn]; char a[maxn],b[maxn]; int main() { int _; cin>>_; while(_--) { scanf("%s %s",a+1,b+1); int lena = strlen(a+1); int lenb = strlen(b+1); memset(dp,0,sizeof dp); dp[0][0] = 1; for(int i = 1;i <= lenb;i++) { if(i == 2 &&b[i] == '*') dp[i][0] = 1; for(int j = 1;j <= lena;j++) { if((b[i] == a[j] || b[i] == '.' )&& dp[i-1][j-1]) dp[i][j] = 1; if(b[i] == '*') { if(dp[i-2][j])//*匹配空 dp[i][j] = 1; if(dp[i-1][j])//*匹配0个 dp[i][j] = 1; if(dp[i-1][j-1] && a[j] == a[j-1])//* 产生的第一个 dp[i][j] = 1; if(dp[i][j-1] && a[j] == a[j-1])// * 产生多个. dp[i][j] = 1; } } } printf("%s\n",dp[lenb][lena] ? "yes" : "no"); } return 0; }
相关文章推荐
- 2017 Multi-University Training Contest - Team 9 1001&&HDU 6161 Big binary tree【树形dp+hash】
- hdu 6170 dp 2017 Multi-University Training Contest - Team 9
- hdu 6053 莫比乌斯反演函数的利用 2017 Multi-University Training Contest - Team 2
- hdu 6055 Regular polygon(判断正方形)(2017 Multi-University Training Contest - Team 2)
- 2017 Multi-University Training Contest - Team 2 && HDU6047
- hdu 6058 Kanade's sum(链表)(2017 Multi-University Training Contest - Team 3 )
- 转载hdu 6059 字典树(好题要慢慢消化)2017 Multi-University Training Contest - Team 3
- hdu 6069 Counting Divisors(约数个数)(2017 Multi-University Training Contest - Team 4 )
- hdu 6070 Dirt Ratio(二分+线段树)(2017 Multi-University Training Contest - Team 4 )
- HDU-6069 Counting Divisors - 2017 Multi-University Training Contest - Team 4(分解质因子区间筛法)
- hdu 6071 Lazy Running(优先队列+dijkstra)(2017 Multi-University Training Contest - Team 4)
- hdu 6073 Matching In Multiplication(2017 Multi-University Training Contest - Team 4 )
- [2017 Multi-University Training Contest - Team 4] HDU 6069 Counting Divisors
- hdu 6034 2017 Multi-University Training Contest - Team 1 模拟题
- HDU-6038 Function - 2017 Multi-University Training Contest - Team 1(构造置换或强连通分量)
- hdu 6034 Balala Power!(贪心)( 2017 Multi-University Training Contest - Team 1 )(无耻之sort)
- hdu 6047 Maximum Sequence(2017 Multi-University Training Contest - Team 2)
- hdu 6045 Is Derek lying?(2017 Multi-University Training Contest - Team 2)
- hdu 6055 Regular polygon(判断正方形)(2017 Multi-University Training Contest - Team 2)
- 2017 Multi-University Training Contest - Team 2 待补