hdu 多校赛 第三场
slove 2/11
rank 288
补题 5/11
---------------------------------------------------
6604 Blow up the city
http://acm.hdu.edu.cn/showproblem.php?pid=6604
题意:给定一个DAG
定义一个点的指挥中心为这个点沿着边走到底的终点
给出q个查询,查询两个点,一次只能删除一个点,
如果删除一个点后输入的两个点之中有任何一个点无法到达原来的指挥中心
则方案数+1,求所有的方案数
思路:
由于是一个DAG,很容易想到建topo序.正向边建一个图,反向边也要建一个图
求topo序时在反向图中加一个超级源点,连接所有入度为0的点(网络流的思路..是不是很像)
定义一个节点的父亲为所有能到达这个节点的祖先的公共祖先.
由topo序,遍历到i点时1~i-1的所有节点已经处理完毕.
定义dep[i]=dep[fa]+1,注意dep不是简单地深度!
用支配树的角度讲就是这个点的支配树到根的链上的节点个数,也就是链长,是不是和深度很像..
然后答案就是dep[a]+dep[b]-dep[LCA(a,b)]
#include<bits/stdc++.h>
#define rep1(i,a,b) for(int i=a;i<=b;++i)
#define rep2(i,b,a) for(int i=b;i>=a;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pf push_front
#define pob pop_back
#define pof pop_front
#define ll long long
using namespace std;
const int MAX = 2e5+5;
const int INF = 0x3f3f3f3f;
int n,m,first1[MAX],nextt1[MAX],u1[MAX],v1[MAX],cnt1=0,du[MAX];
int first2[MAX],nextt2[MAX],u2[MAX],v2[MAX],top[MAX],cnt2=0,rt;
int tot=0,f[MAX][30],dep[MAX];
void add1(int a,int b){
u1[cnt1]=a,v1[cnt1]=b;
nextt1[cnt1]=first1[u1[cnt1]];
first1[u1[cnt1]]=cnt1;++cnt1;
}
void add2(int a,int b){
u2[cnt2]=a,v2[cnt2]=b;
nextt2[cnt2]=first2[u2[cnt2]];
first2[u2[cnt2]]=cnt2;++cnt2;
}
void topology(){
deque<int> list1;
rep1(i,1,n) if(!du[i]) list1.pb(i);
while(!list1.empty()){
int now = list1.front();list1.pof();
top[++tot]=now;
for(int num = first2[now];num!=-1;num=nextt2[num]){
--du[v2[num]];
if(!du[v2[num]]) list1.pb(v2[num]);
}
}
}
int LCA(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
rep2(i,20,0) if(dep[y]>dep[x]&&dep[f[y][i]]>=dep[x]) y=f[y][i];
rep2(i,20,0) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return x==y?x:f[x][0];
}
void solve(){
int t;
scanf("%d",&t);
while(t--){
cnt1=cnt2=tot=0;
mem(first1,-1);mem(first2,-1);mem(du,0);mem(f,0);mem(dep,0);
scanf("%d%d",&n,&m); rt=n+1;
rep1(i,1,m){
int a,b;
scanf("%d%d",&a,&b);
add1(a,b),add2(b,a); ++du[a];
}
rep1(i,1,n) if(du[i]==0) add1(i,rt),add2(rt,i);
topology();
rep1(i,1,n){
int now = top[i],fa=-1;
for(int num = first1[now];num!=-1;num=nextt1[num])
fa=fa==-1?v1[num]:LCA(v1[num],fa);
dep[now]=dep[fa]+1; f[now][0]=fa;
rep1(j,1,20) f[now][j]=f[f[now][j-1]][j-1];
}
int q;
scanf("%d",&q);
while(q--){
int a,b;
scanf("%d%d",&a,&b);
int ans = dep[a]+dep[b]-dep[LCA(a,b)];
printf("%d\n",ans);
}
}
}
int main(void)
{
solve();
return 0;
}
6606 Distribution of books
http://acm.hdu.edu.cn/showproblem.php?pid=6606
题意:n个数,连续的一段分为一份,总共分为k份,要求每份和的最大值尽量小
1<=n<=2e5,-1e9<=ai<=1e9,1<=k<=n
note:最后不一定分完,但前缀一定要分完。
做法:
1、考虑二分答案,check的时候dp[i] 表示区间 最多能被分为多少份,
2、假设二分的答案是kk,然后对于 dp[i] 来说,他能被分成的份数,就是所有大于sum[i]-kk 能被分成的份数+1,可以这样理解,如果他的一个前缀大于 sum[i]-kk ,那么 i 和 j 之间的和一定是小于等于 kk 的,就是说可以构成一份。
3、由于可能存在构成0份的情况,比如所有值都比 kk 要大,所以预设最大值为极小值,当某个前缀和值小于等于 kk 时,那么这个点最少可以分成一份,其他点也会由这个点转移,因为其他店是极小的,不能构成一份。
4、直接dp复杂度 ,显然超时,所以用线段树来维护前缀和最大值,这样复杂度
#include <bits/stdc++.h>
#define lson left,mid,k<<1
#define rson mid+1,right,k<<1|1
#define imid int mid=(left+right)/2;
#define ll long long
using namespace std;
const int MAXN = 2e5 + 5;
struct node
{
int l;
int r;
ll maxn;
}que[MAXN * 4];
int n, k, ql, qr, qq;
ll a[MAXN], t[MAXN], val;
void up(int k)
{
que[k].maxn = max(que[k << 1].maxn, que[k << 1 | 1].maxn);
}
void build(int left = 1, int right = n, int k = 1)
{
que[k].l = left;
que[k].r = right;
if (left == right)
{
que[k].maxn = -1e18;
return;
}
imid;
build(lson);
build(rson);
up(k);
}
void update(int left = 1, int right = n, int k = 1)
{
if (qr < left || right < ql)
return;
if (ql <= left && right <= qr)
{
que[k].maxn = max(que[k].maxn, val);
return;
}
imid;
update(lson);
update(rson);
up(k);
}
ll query(int left = 1, int right = n, int k = 1)
{
if (qr < left || right < ql)
return -1e18;
if (ql <= left && right <= qr)
return que[k].maxn;
imid;
return max(query(lson), query(rson));
}
bool check(ll kk)
{
build();
for (int i = 1; i <= n; i++)
{
/*
dp[i]表示前i个数最多能分成几段
for (int i = 1; i <= n; i++)
{
dp[i] =max(dp[j]) + 1;
(sum[i]-sum[j]<=mid)
sum[j]>=sum[i]-mid
}
*/
ll temp = t[a[i]] - kk;
temp = lower_bound(t + 1, t + 1 + qq, temp) - t;//离散化
ql = temp; qr = n;
val = query() + 1;//找到所有大于sum[i]-kk的最大组数
if (t[a[i]] <= kk)//单个物品合法,所以这个点的最小组数是1
val = max(val, 1LL);
if (val >= k)
return true;
ql = a[i], qr = a[i];//更新前缀和为a[i]的最大组数val
update();
}
return false;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
a[i] += a[i - 1];
t[i] = a[i];
}
sort(t + 1, t + 1 + n);
qq = unique(t + 1, t + 1 + n) - t - 1;
for (int i = 1; i <= n; i++)
a[i] = lower_bound(t + 1, t + 1 + qq, a[i]) - t;
ll l = -2e14 - 5, r = 2e14 + 5;
while (l + 1 < r)
{
ll mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid;
}
if (!check(l))
l = r;
printf("%lld\n", l);
}
}
6608 Fansblog
http://acm.hdu.edu.cn/showproblem.php?pid=6608
题意:给一个质数N,找出小于N的最大的质数P,并且输出P的阶乘模N。
思路:米勒罗宾或者暴力找出小于N的最大的质数P。
由威尔逊定理 ,我们要求
Code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll ModMul(ll a, ll b, ll n)
{
ll ans = 0;
while (b)
{
if (b & 1)
ans = (ans + a) % n;
a = (a + a) % n;
b >>= 1;
}
return ans;
}
ll ModExp(ll a, ll b, ll n)
{
ll ans = 1;
while (b)
{
if (b & 1)
ans = ModMul(ans, a, n);
a = ModMul(a, a, n);
b >>= 1;
}
return ans;
}
bool miller_rabin(ll n)
{
ll i, j, a, x, y, t, u, s = 10;
if (n == 2)
return true;
if (n < 2 || !(n & 1))
return false;
for (t = 0, u = n - 1; !(u & 1); t++, u >>= 1);
for (i = 0; i < s; i++)
{
a = rand() % (n - 1) + 1;
x = ModExp(a, u, n);
for (j = 0; j < t; j++)
{
y = ModMul(x, x, n);
if (y == 1 && x != 1 && x != n - 1)
return false;
x = y;
}
if (x != 1)
return false;
}
return true;
}
ll MOD = 1000000007;
ll inv(ll a)
{
return ModExp(a, MOD - 2, MOD);
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
ll n;
scanf("%lld", &n);
MOD = n;
ll p = n - 1;
while (true)
{
if (miller_rabin(p))
break;
else
p--;
}
ll ans = 1;
if (p + 2 == n)
{
printf("1\n");
continue;
}
for (ll i = p + 1; i <= n - 2; i++)
ans = ModMul(ans, inv(i), MOD);
printf("%lld\n", ans);
}
}
6609 Find the answer
http://acm.hdu.edu.cn/showproblem.php?pid=6609
题意:以1为左端点,i为右端点,要满足和小于等于M,至少需要将多少个数字变成0?
思路:二分取的数值的最大值,然后判一下能否取到下一个数值,取几个(就是说,下一个数值可能没有全部取,只需一部分),树状数组或者线段树维护前缀和和前缀个数。需要离散化
(Codeforces原题链接 :http://codeforces.com/contest/1185/problem/C2)数据弱化版本
Code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5 + 5;
ll mp[MAXN];
int a[MAXN];
int t[MAXN];
ll c_cnt[MAXN];
ll c_sum[MAXN];
int lowbit(int k)
{
return k& (-k);
}
void update(int k, ll x, ll val)
{
while (k < MAXN)
{
c_cnt[k] += x;
c_sum[k] += val;
k += lowbit(k);
}
}
ll query(int k)
{
ll ans = 0;
while (k)
{
ans += c_sum[k];
k -= lowbit(k);
}
return ans;
}
ll querycnt(int k)
{
ll ans = 0;
while (k)
{
ans += c_cnt[k];
k -= lowbit(k);
}
return ans;
}
bool check(int k, ll m)
{
ll ans = query(k);
if (ans <= m)
return true;
return false;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int n;
ll m;
scanf("%d%lld", &n, &m);
for (int i = 1; i <= n; i++)
{
c_cnt[i] = 0;
c_sum[i] = 0;
scanf("%lld", &a[i]);
t[i] = a[i];
}
sort(t + 1, t + 1 + n);
int qq = unique(t + 1, t + 1 + n) - t - 1;
for (int i = 1; i <= n; i++)
{
int temp = lower_bound(t + 1, t + 1 + qq, a[i]) - t;
mp[temp] = a[i];
a[i] = temp;
}
printf("0 ");
for (int i = 2; i <= n; i++)
{
update(a[i - 1], 1, mp[a[i - 1]]);
int l = 0, r = qq;
while (l + 1 < r)
{
int k = (l + r) / 2;
if (check(k, m - mp[a[i]]))
l = k;
else
r = k;
}
if (check(r, m - mp[a[i]]))
l = r;
ll ans = querycnt(l);
ll sum = query(l);
if (l < qq)
{
ll cnt = querycnt(l + 1) - querycnt(l);
int num = min((m - mp[a[i]] - sum) / t[l + 1], cnt);
ans += num;
}
printf("%lld ", i - ans - 1);
}
printf("\n");
}
}
6611 K Subsequence
题意:给你一个序列,让你取出k个不降子序列,求最大权值和,一个元素只能取一次
思路: 经典网络24流里有一题是最长递增子序列.那个题意是先让你求出一个序列的最长递增子序列
长度是多少,求出的答案为s,然后让你求原序列有多少个长度为s的递增序列.
那个题目的思路是拆点法.先建一个源点和汇点,然后将每个序列中的点拆成ix,iy.
源点和每个ix连有边权为1的边,ix和iy之间有一条边权为1的边,然后当j>i&&a[j]>=a[i]时,连一条
iy->jx的边,然后根据之前的DP让每个dp[i]==s的点和汇点连一条边权为1的边,然后跑一边网络流
那么这个题目就和上个题目很相似了.题目是让我们求最大权值和,如果做过上题的话显然可以想到
费用流.那这就好办啦,题目要求是k个,先把源点拆成sl,sr,然后sl,sr的边权为k.
然后把序列的每个点拆成ix,iy,和上题的思路一样,ix和iy之间有一条边权为1的边,
然后当j>i&&a[j]>=a[i]时,连一条iy->jx的边,然后sr->ix,iy->t都有一条权为1的边,然后
这是费用流,肯定要有费用的!只需要把ix->iy边的费用设为该点的权值,其他边的费用都为0
然后跑一边费用流就好啦
有一个点需要注意:ix->iy和 当j>i&&a[j]>=a[i]时,连一条iy->jx的边,这两种边不能搞反了
考虑一种特殊情况,整个原序列为 6 5 4 3 2 1,如果把上述边反向建的话你就会发现跑不到汇点了,
答案为0,显然答案不为0.
#include<bits/stdc++.h>
#define rep1(i,a,b) for(int i=a;i<=b;++i)
#define rep2(i,b,a) for(int i=b;i>=a;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pf push_front
#define pob pop_back
#define pof pop_front
#define sc scanf
#define pr printf
#define ll long long
using namespace std;
typedef pair<int,int> P;
const int MAXN = 1e5+5;
const int INF = 0x3f3f3f3f;
struct side{ int to,cap,cost,rev;};
int n,m,flow,s,t,s1,h[MAXN],mincost;
vector<side> first[MAXN];
int dis[MAXN],sideOne[MAXN],sideTwo[MAXN],a[MAXN];
void minCostMAXFlow(int f)
{
fill(h,h+t,0);
while(f > 0){
priority_queue<P,vector<P>, greater<P> > list1;
rep1(i,0,t) dis[i]=INF;dis[s] = 0;
list1.push(P(0,s));
while(!list1.empty()) {
P now = list1.top(); list1.pop();
if(dis[now.second] < now.first) continue;
int v; v = now.second;
for(int i=0;i<(int)first[v].size();++i){
side &e = first[v][i];
if(e.cap > 0 && dis[e.to] > dis[v] + e.cost + h[v] - h[e.to]){
dis[e.to] = dis[v] + e.cost + h[v] - h[e.to];
sideOne[e.to] = v,sideTwo[e.to] = i;
list1.push(P(dis[e.to],e.to));
}
}
}
if(dis[t] == INF) break;
rep1(i,0,t) h[i] += dis[i];
int d = f;
for(int v=t;v!=s;v=sideOne[v]) d = min(d,first[sideOne[v]][sideTwo[v]].cap);
f=f-d,flow =flow+ d,mincost =mincost+ d * h[t];
for(int v=t;v!=s;v=sideOne[v]){
side &e = first[sideOne[v]][sideTwo[v]];
e.cap -= d,first[v][e.rev].cap += d;
}
}
}
void add(int from,int to,int cap,int cost){
first[from].pb({to,cap,cost,(int)first[to].size()});
first[to].pb({from,0,-cost,(int)first[from].size()-1});
}
void solve(){
int tt;sc("%d",&tt);
while(tt--){
flow=mincost=0;
scanf("%d%d",&n,&m);
rep1(i,1,2*n+3) first[i].clear();
rep1(i,1,n) sc("%d",&a[i]);
s=2*n+1,s1=2*n+2,t=2*n+3;
add(s,s1,m,0);
rep1(i,1,n) add(s1,i,1,0),add(i,i+n,1,-a[i]),add(i+n,t,1,0);
rep1(i,1,n) rep1(j,i+1,n) if(a[i]<=a[j]) add(i+n,j,1,0);
minCostMAXFlow(INF);
pr("%d\n",-mincost);
}
}
int main(void)
{
solve();
return 0;
}