题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6604
题意:有 n 个城市,m 条有向边。定义特殊结点为:不存在出度的边。现在有 q 组询问,每一组询问给出两个结点 u 和 v,你可以破坏任意一个城市,使得如果 u 和 v 中至少有一个不能到达特殊结点,就算破坏成功。对于每组询问,回答有多少种成功的破坏方案。
思路:大致思路就是算出破坏 u 的方案数,然后算出破坏 v 的方案数,最后减去同时破坏两个的方案数。学了支配树之后就会做了。把图反向连边,然后新增一个结点向所有入度为 0 (反向连边后的图)连边,然后以新增节点为根结点构建支配树,那么支配树中某个结点的祖先就是成功的方案。所以计算每个结点的深度之后,最后答案就是 d[u] + d[v] - d[LCA(u, v)] 。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> P;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
struct dominant_tree
{
#define maxn 100005
VI a[maxn],b[maxn],c[maxn],t[maxn];
int dfn[maxn],which[maxn],f[maxn],cnt,n,root;
int sdom[maxn],idom[maxn],far[maxn],val[maxn];
void dfs(int u,int fa)
{
dfn[u]=++cnt; which[cnt]=u; f[u]=fa;
for(int v:a[u]) if(!dfn[v]) dfs(v,u);
}
int find(int x)
{
if(x==far[x]) return x;
int t=find(far[x]);
if(dfn[sdom[val[far[x]]]]<dfn[sdom[val[x]]]) val[x]=val[far[x]];
return far[x]=t;
}
void tarjan()
{
REP_(i,cnt,2)
{
int u=which[i];
for(int v:b[u]) if(dfn[v])
{
find(v);
if(dfn[sdom[val[v]]]<dfn[sdom[u]]) sdom[u]=sdom[val[v]];
}
c[sdom[u]].pb(u);
far[u]=f[u]; u=f[u];
for(int v:c[u])
{
find(v);
if(sdom[val[v]]==u) idom[v]=u;
else idom[v]=val[v];
}
}
REP(i,2,cnt)
{
int u=which[i];
if(idom[u]!=sdom[u]) idom[u]=idom[idom[u]];
}
}
void build(int n,int root,vector<P> &E)
{
this->n=n; this->root=root; cnt=0;
REP(i,0,n) a[i].clear(),b[i].clear(),c[i].clear(),t[i].clear();
REP(i,0,n) sdom[i]=val[i]=far[i]=i;
REP(i,0,n) idom[i]=dfn[i]=f[i]=which[i]=0;
for(P e:E)
{
int u=e.first,v=e.second;
a[u].pb(v); b[v].pb(u);
}
dfs(root,0);
tarjan();
REP(i,1,n) if(i!=root) t[idom[i]].pb(i);
}
}xx;
int du[maxn],d[maxn],siz[maxn],f[maxn],son[maxn];
int dfn[maxn],which[maxn],top[maxn],cnt;
void dfs1(int u,int fa,int depth)
{
f[u]=fa; d[u]=depth; siz[u]=1; son[u]=0;
for(int v:xx.t[u])
{
if(v==fa) continue;
dfs1(v,u,depth+1);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tf)
{
top[u]=tf; dfn[u]=++cnt; which[cnt]=u;
if(!son[u]) return;
dfs2(son[u],tf);
for(int v:xx.t[u])
{
if(v!=f[u] && v!=son[u]) dfs2(v,v);
}
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
d[top[x]]>d[top[y]]?(x=f[top[x]]):(y=f[top[y]]);
return d[x]<d[y]?x:y;
}
int main()
{
int T=read();
while(T--)
{
int n=read(),m=read();
REP(i,1,n) du[i]=0;
cnt=0;
vector<P> E;
while(m--)
{
int u=read(),v=read();
E.pb(P(v,u));
du[u]++;
}
REP(i,1,n) if(!du[i]) E.pb(P(n+1,i));
xx.build(n+1,n+1,E);
dfs1(n+1,0,0);
dfs2(n+1,n+1);
int q=read();
while(q--)
{
int a=read(),b=read();
printf("%d\n",d[a]+d[b]-d[LCA(a,b)]);
}
}
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6606
题意:给出 n 本书,每一本书有一个值 a[i],现在要把前若干本书(可以是前 n 本或者更少,这个自己决定)分割成 k 段,每一段的值为这一段所有书的值之和,而所有段的最大值为分割的代价。求最小代价。
思路:显然要二分最小代价,然后对于某个代价 x,要求把书分成 k 段,每一段的和不超过 x。一开始我的做法是贪心,但是后来发现是错的,比如对于 -3 -3 2 -4
要分成两段,如果用贪心发现 -4 是不行的,但实际上可以。
考虑 dp,设 f(i) 表示前 i 本书最多可以分成多少段,那么只要存在 s i − s j ≤ x s_i-s_j\le x si−sj≤x(其中 s 为 a 的前缀和)就可以有转移 f i = f j + 1 f_i=f_j+1 fi=fj+1 。上述条件可以转化为 s j ≥ s i − x s_j \ge s_i-x sj≥si−x 。所以思路就很明显了:用一个类似权值线段树的东西维护,首先把所有的 s 离散化,然后每次算完 f[i] 之后更新对应 s[i] 的权值为 f[i];然后每次要计算 f[i] 时,找到满足 s j ≥ s i − x s_j \ge s_i-x sj≥si−x 的区域的最大值,i 就从那里转移。复杂度 O ( n ( l o g n ) 2 ) O(n(logn)^2) O(n(logn)2) 。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
template <class T>
struct segment_tree_max
{
#define chl (k<<1)
#define chr (k<<1|1)
#define mid ((l+r)>>1)
#define inf 2147483647
T *t;
int n;
segment_tree_max(int n) {t=new T[n<<2](); this->n=n;}
void del() {delete[] t;}
void build(int k,int l,int r)
{
if(l==r) {t[k]=-1; return;}
build(chl,l,mid); build(chr,mid+1,r);
push_up(k);
}
void build() {build(1,0,n);}
void push_up(int k) {t[k]=max(t[chl],t[chr]);}
void update(int k,int l,int r,int id,T x)
{
if(l>r || id<l || id>r) return;
if(l==r && l==id) {t[k]=x; return;}
update(chl,l,mid,id,x); update(chr,mid+1,r,id,x);
push_up(k);
}
void update(int id,T x) {update(1,0,n,id,x);}
T query(int k,int l,int r,int ll,int rr)
{
if(l>rr || ll>r) return -inf;
if(l>=ll && r<=rr) return t[k];
return max(query(chl,l,mid,ll,rr),query(chr,mid+1,r,ll,rr));
}
T query(int ll,int rr) {return query(1,0,n,ll,rr);}
};
const int maxn=2e5+5;
int n,k,a[maxn],len,f[maxn];
LL s[maxn],b[maxn];
int ID(LL x) {return lower_bound(b+1,b+len+1,x)-b;}
bool can(LL x)
{
segment_tree_max<int> t(len);
t.build();
t.update(ID(0),0);
REP(i,1,n)
{
int q=t.query(ID(s[i]-x),len);
if(q>=0) f[i]=q+1;
else f[i]=-1;
t.update(ID(s[i]),f[i]);
if(f[i]>=k) {t.del(); return 1;}
}
return 0;
}
int main()
{
int T=read();
while(T--)
{
n=read(),k=read();
REP(i,1,n) a[i]=read(),s[i]=s[i-1]+a[i],b[i]=s[i];
b[n+1]=0; len=n+1;
sort(b+1,b+len+1);
len=unique(b+1,b+len+1)-b-1;
LL l=-1e15+5,r=1e15+5;
while(l<r-1)
{
if(can(mid)) r=mid;
else l=mid;
}
printf("%lld\n",r);
}
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6608
题意:给出一个质数 P( 1 e 9 ≤ P ≤ 1 e 14 1e9\le P \le 1e14 1e9≤P≤1e14),找到最大的比 P 小的质数 Q,然后计算 Q ! m o d P Q! \ mod \ P Q! mod P 。
思路:根据威尔逊定理,一个数是质数的等价条件是 ( P − 1 ) ! ≡ − 1 ( m o d P ) (P-1)! \equiv -1 (mod \ P) (P−1)!≡−1(mod P) 。然后质数之间的间隔也不是很大,所以就从 P-1 开始往下枚举找到下一个质数,然后计算就行了。这里要注意 long long 会炸精度,所以要用龟速乘。然后判断素数可以用费马素性测试。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
LL multi(LL x,LL y,LL M)
{
LL ret=0;
while(y)
{
if(y&1) ret=(ret+x)%M;
x=(x<<1)%M;
y>>=1;
}
return ret;
}
LL ksm(LL x,LL n,LL M)
{
LL ret=1;
while(n)
{
if(n&1) ret=multi(ret,x,M);
x=multi(x,x,M);
n>>=1;
}
return ret;
}
bool is_prime(LL x)
{
REP(i,1,100)
{
if(ksm(i,x-1,x)!=1) return 0;
}
return 1;
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
LL P;
while(T--)
{
cin>>P;
LL Q=P-1;
while(!is_prime(Q)) Q--;
LL ans=P-1;
for(LL x=P-1;x>Q;x--) ans=multi(ans,ksm(x,P-2,P),P);
cout<<ans<<endl;
}
return 0;
}
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6609
题意:给出一个数组 a,对于每个 i,可以找出若干个小于 i 的下标,然后把这些下标对应的值变为 0 。问对于每个 i,至少变多少个为 0,才能使 a[1,…,i] 的和小于等于 m。
思路:一开始先求出 a 中每个数的排名,然后维护两个树状数组,一个维护前 i 个数的和(按照排名来维护),另一个维护排名中哪些排名已经被选中了。从前往后遍历 a,然后二分查找第一个树状数组,找到最大的一个 sum[x]<=m-a[i] 的 x,然后第二个树状数组的前 x 项和为 s,i - 1 - s 就是第 i 个数的答案。
复杂度 O ( n ( l o g n ) 2 ) O(n(logn)^2) O(n(logn)2) ,然而可以把两个树状数组合起来,就不用二分那一步,因为在搜索最大的 x 这一步可以直接边搜索边记录已经累加的和,可以把复杂度降到 O ( n l o g n ) O(nlogn) O(nlogn) ,不过原本的也过了,可能是树状数组常数很小的缘故吧。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
// Binary Indexed Tree
template<class T>
struct BIT
{
T *a,*c;
int n;
BIT(int n) {this->n=n; a=new T[n+5](); c=new T[n+5]();}
int lowbit(int w) {return w&(-w);}
T sum(int w) {T ret=0; while(w>0) ret+=c[w],w-=lowbit(w); return ret;}
void update(int w,T x) {a[w]+=x; while(w<=n) c[w]+=x,w+=lowbit(w);}
T sum(int l,int r) {return sum(r)-sum(l-1);}
};
const int maxn=2e5+5;
int a[maxn],w[maxn];
pair<int,int> b[maxn];
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
int n=read(),m=read();
REP(i,1,n) a[i]=read(),b[i]=make_pair(a[i],i);
sort(b+1,b+n+1);
REP(i,1,n) w[b[i].second]=i;
BIT<LL> ts(n);
BIT<int> tt(n);
REP(i,1,n)
{
int l=0,r=n+1;
while(l<r-1)
{
int mid=(l+r)>>1;
if(ts.sum(mid)<=m-a[i]) l=mid;
else r=mid;
}
printf("%d ",i-1-tt.sum(l));
ts.update(w[i],a[i]);
tt.update(w[i],1);
}
puts("");
}
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6611
题意:有一个长度为 n( 1 ≤ n ≤ 2000 1\le n\le 2000 1≤n≤2000) 的数组,每次操作可以从中选取一个单调不减的子序列,然后把子序列的和累加到答案中,然后把子序列从原数组中删去。这样的操作最多进行 k( 1 ≤ k ≤ 10 1\le k\le 10 1≤k≤10) 次。问最后答案的最大值。多组数据。
思路:一开始我的想法是,贪心地选取:每次操作都选择和最大的子序列,然后把它删去,这样选取 k 次。每次选取都可以用一个 O ( n 2 ) O(n^2) O(n2) 的 dp 算出来,然后复杂度也对。但是这样做是错的,比如:6 4 4 8 5
,k=2 。如果用贪心的做法,第一次会选取 4 4 8
,第二次会选取 6
;而最优的解肯定是 6 8
和 4 4 5
。
后来看到很多博客说,求多个不相交的子序列,很容易想到网络流(黑人问号???表示之前从来没有这种思路)
不过网络流确实可以搞这种问题,在一定的条件下(这里是 ∀ i < j , a i ≤ a j \forall i
这里还要考虑 a[i] 这个权值,所以给拆点之间的连边加一个 -a[i] 的费用,其它边费用为 0,那么在流量为 k 的前提下的最小费用的负值,就是最终答案。不得不说这种建模方式真厉害。
然而传统的 SPFA 的最小费用流在这里被卡了,还要 Dijkstra 优化最短路的过程。原本每次 SPFA 是为了以费用为权值在还有容量的边中(这个好像叫残余网络?)求 s 到 t 的最短路,然后在这条最短路上增广。但是由于反向边图中存在负权边,一般的 Dijkstra 不适用,所以要仿照 Johnson 算法给每个结点增加一个势函数 H,然后原本 边的权值 w 就变成了 w+H(u)-H(v) 。这里势函数是把每次求出的 dis 累加,然后最短路增广时用的是势函数,这里我一直没想明白为什么,可能是因为我还不理解网络流,只知道怎么传统地用的缘故吧。(先放在这儿,以后再回来看)
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=2e4+5,inf=1e9;
struct edge {int to,cap,rev,cost;};
vector<edge> G[maxn];
int dis[maxn],book[maxn],prevv[maxn],preve[maxn],n,a[maxn],k,H[maxn];
void add_edge(int from,int to,int cap,int cost)
{
G[from].push_back((edge){to,cap,(int)G[to].size(),cost});
G[to].push_back((edge){from,0,(int)G[from].size()-1,-cost});
}
int min_cost_flow(int n,int s,int t,int flow)
{
int ans=0;
while(flow>0)
{
typedef pair<int,int> P;
priority_queue<P,vector<P>,greater<P> > Q;
fill(dis,dis+n+1,inf);
dis[s]=0;
Q.push(P(0,s));
while(!Q.empty())
{
P p=Q.top(); Q.pop();
int d=p.first,u=p.second;
if(dis[u]<d) continue;
REP(i,0,G[u].size()-1)
{
edge e=G[u][i];
int v=e.to;
if(e.cap>0 && dis[v]>d+e.cost+H[u]-H[v])
{
dis[v]=d+e.cost+H[u]-H[v];
prevv[v]=u; preve[v]=i;
Q.push(P(dis[v],v));
}
}
}
if(dis[t]==inf) return -1;
REP(i,1,n) H[i]+=dis[i];
int d=flow;
for(int v=t;v!=s;v=prevv[v]) d=min(d,G[prevv[v]][preve[v]].cap);
flow-=d;
ans+=d*H[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=G[prevv[v]][preve[v]];
e.cap-=d;
G[v][e.rev].cap+=d;
}
}
return ans;
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
n=read(),k=read();
REP(i,1,n) a[i]=read();
int s=n*2+1,t=n*2+2;
REP(i,1,t) G[i].clear(),H[i]=0;
REP(i,1,n) add_edge(s,i,1,0),add_edge(i+n,t,1,0);
REP(i,1,n) add_edge(i,i+n,1,-a[i]);
REP(i,1,n) REP(j,i+1,n) if(a[i]<=a[j]) add_edge(i+n,j,1,0);
printf("%d\n",-min_cost_flow(t,s,t,k));
}
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址:
题意:
思路:
代码: