[LibreOJ 2316][NOIP2017]逛公园
给你一张\(n\)个点\(m\)条边的有向图,设从\(1\)到\(n\)的最短路为\(d\),那么请问有多少\(1\)到\(n\)的路径(可以是非简单路径)满足其长度在\([d, d + K]\)中,如果答案为无限大的话输出\(-1\)。
\(1\leq n\leq 10^5, 1\leq m\leq 2\times 10^5, 0\leq K\leq 50\)。
[UOJ 333][NOIP2017]宝藏
啊啊啊爽到力,,,
过去太池沼力,没有做出来惹。现在看了看可能比较简单罢……
定义状态\(d_{i, S}\)表示当前已经被覆盖的点集为\(S\),然后当前的生成树已经考虑到了第\(i\)层,转移看起来很毒……
所以预处理一个点集到另一个点集只走一步边来扩展,最优解是多少,然后转移就方便力……
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> const int maxn = 12; int G[15][15]; const int INF = 0x3f3f3f3f; void add_edge(int u, int v, int d) { G[u][v] = std::min(G[u][v], d); G[v][u] = std::min(G[v][u], d); } int n; int g1[(1 << maxn)][15], g2[(1 << maxn)][(1 << maxn)]; void process() { for(int s = 1; s < (1 << n); s ++) { for(int i = 1; i <= n; i ++) { g1[s][i] = INF; for(int j = 0; j < n; j ++) { if((1 << j) & s) { g1[s][i] = std::min(g1[s][i], G[j + 1][i]); } } } } for(int s = 1; s < (1 << n); s ++) { int k = (1 << n) - 1; k = k & (~s); for(int t = k; t > 0; t = (t - 1) & k) { g2[s][t] = 0; for(int i = 1; i <= n; i ++) { if((1 << (i - 1)) & t) { if(g1[s][i] == INF) { g2[s][t] = INF; break; } g2[s][t] += g1[s][i]; } } } } } using ll = long long; ll d[14][(1 << maxn)]; ll dp() { for(int s = 0; s < (1 << n); s ++) { d[1][s] = INF; } for(int i = 0; i < n; i ++) { d[1][(1 << i)] = 0; } for(int i = 2; i <= n; i ++) { for(int s = 1; s < (1 << n); s ++) { d[i][s] = INF; for(int t = (s - 1) & s; t != 0; t = (t - 1) & s) { d[i][s] = std::min(d[i][s], d[i - 1][t] + (ll(i - 1)) * g2[t][s ^ t]); } } } ll ans = INF; for(int i = 1; i <= n; i ++) { ans = std::min(ans, d[i][(1 << n) - 1]); } return ans; } int main() { memset(G, 0x3f, sizeof(G)); int m; scanf("%d%d", &n, &m); for(int i = 1; i <= m; i ++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); add_edge(u, v, w); } process(); printf("%lld\n", dp()); return 0; }
[CodeVS 1217]借教室
很容易想到线段树水过。
很可惜,这样估计也就70分。
然后这个问题是符合单调性的……可以二分答案哒。不过怎么判定?
不知道诸位还记得差分序列?记得就好,用差分序列的方法维护即可。
等等,我怎么感觉你这个做法是[tex]O(nlogn)[/tex]的,能过?
推荐一个行之有效的优化:注意一下每一次修改都是在之前的基础上反悔或前进几次,所以就可以直接在原基础上修改差分序列辣~这样效率是很高滴~
代码:
#include <cstdio> const int maxn=1e6+5; typedef long long ll; ll A[maxn]; int l[maxn],r[maxn]; ll d[maxn]; ll C[maxn]; int n,m,last=0; bool Check(int x){ register int i,cnt=0; if(x>last) for(i=last+1;i<=x;i++){ C[l[i]]+=d[i]; C[r[i]+1]-=d[i]; } if(x<last) for(i=x+1;i<=last;i++){ C[l[i]]-=d[i]; C[r[i]+1]+=d[i]; } last=x; for(i=1;i<=n;i++){ cnt+=C[i]; if(cnt>A[i]) return false; } return true; } int main(){ register int i,L,M,R; scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%lld",&A[i]); } for(i=1;i<=m;i++){ scanf("%lld%d%d",&d[i],&l[i],&r[i]); } L=1; R=m+1; while(L<R){ M=L+(R-L)/2; if(Check(M)){ L=M+1; }else{ R=M; } } if(L==m+1) puts("0"); else printf("-1\n%d\n",L); return 0; }
[CodeVS 3729]飞扬的小鸟
论如何把一个完全背包出的高大上……
这个题啊……细节特别多……
首先,不建议开滚动数组,细节处理就会疯。
然后呢……真的不太好说了……还是看代码吧(尽管代码还是有明显的滚动数组痕迹)。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn=10005,maxm=1005; const int INF=0x7f7f7f7f; #define REP(i,n) for(i=0;i<(n);i++) #define REP_B(i,n) for(i=1;i<=(n);i++) int GZ[maxn][2]; bool haveGZ[maxn]; int A[maxn][2]; int d[maxn][maxm]; int main(){ int n,m,k,temp; register int i,j,v,now,pre,ans=INF,cnt; scanf("%d%d%d",&n,&m,&k); cnt=k; REP(i,n){ scanf("%d%d",&A[i][0],&A[i][1]); } for(i=0;i<=n;i++){ GZ[i][0]=0; GZ[i][1]=m+1; } REP_B(i,k){ scanf("%d",&temp); haveGZ[temp]=true; scanf("%d%d",&GZ[temp][0],&GZ[temp][1]); } memset(d,0x7f,sizeof(d)); memset(d[0],0,sizeof(d[0])); d[0][0]=INF; REP_B(i,n){ now=i; pre=i-1; d[now][0]=INF; int& X=A[i-1][0]; int& Y=A[i-1][1]; REP_B(j,m){ if(j-X>0 && d[pre][j-X]<INF) d[now][j]=min(d[now][j],d[pre][j-X]+1); if(j-X>0 && d[now][j-X]<INF) d[now][j]=min(d[now][j],d[now][j-X]+1); if(j==m) for(v=j-X;v<=m;v++){ if(d[pre][v]<INF) d[now][j]=min(d[now][j],d[pre][v]+1); if(d[now][v]<INF) d[now][j]=min(d[now][j],d[now][v]+1); } } for(j=GZ[i][0]+1;j<GZ[i][1];j++){ if(j+Y<=m && d[pre][j+Y]<INF) d[now][j]=min(d[now][j],d[pre][j+Y]); } if(haveGZ[i]){ REP_B(j,GZ[i][0]) if(d[now][j]<INF){ d[now][j]=INF; } for(j=GZ[i][1];j<=m;j++) if(d[now][j]<INF){ d[now][j]=INF; } } } for(i=n;i>=1;i--){ for(j=m;j>=1;j--){ ans=min(ans,d[i][j]); } if(ans<INF) break; if(haveGZ[i]) cnt--; } if(cnt==k){ printf("1\n%d\n",ans); }else{ printf("0\n%d\n",cnt); } return 0; }
[BZOJ 4326]运输计划
很容易想到树剖直接搞一发的玩法。估计复杂度是[tex]O(nlog^2n)[/tex]的,有点吃不消,再加上会被卡常,咔嚓掉。
考虑二分答案。我们可以直接二分最终答案,那么如何验证?
假设验证谓词为[tex]C(t)[/tex],表示通过把一条边清零可以使得用时为[tex]t[/tex]。那么所有用时大于[tex]t[/tex]的计划都要被清掉一条边。可见我们需要清零的边是所有用时大于[tex]t[/tex]的计划的边的交集中的最大边。
那么如何求交集呢?可以考虑树上差分,对于每次计划[tex](u,v)[/tex],我们求出其最近公共祖先[tex]L[/tex],然后对[tex]d[u][/tex]和[tex]d[v][/tex]分别加一,对[tex]d[L][/tex]则减二。随后将d上标记上推,可以求出每个点连向父亲的边被几个计划经过。易求交集。
至此方向已明确:二分答案,树上差分验证,同时我们还要用到LCA。LCA建议使用总计[tex]O(n)[/tex]的TarjanLCA离线算法,倍增也可。
代码:
/************************************************************** Problem: 4326 User: danihao123 Language: C++ Result: Accepted Time:4756 ms Memory:62188 kb ****************************************************************/ #include <cstdio> #include <cctype> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int maxn=300001,maxm=300001*2; const int BUFSIZE=10000001; #define REP(i,n) for(i=0;i<(n);i++) #define REP_B(i,n) for(i=1;i<=(n);i++) #define GRAPH_REP(i,u) for(i=first[(u)];i;i=next[i]) #define CUS_REP(i,a,b) for(i=(a);i<=(b);i++) int n; namespace FastIO{ char buf[BUFSIZE]; int IO_tot=0; inline void InitFastIO(){ fread(buf,sizeof(char),BUFSIZE-1,stdin); } inline char GetC(){ return buf[IO_tot++]; } inline int readint(){ register int x=0; register char c=GetC(); while(!isdigit(c)) c=GetC(); while(isdigit(c)){ x=10*x+c-'0'; c=GetC(); } return x; } int bf[10]; inline void putint(int x){ register int siz=0,i; if(!x){ bf[siz++]=0; }else{ while(x){ bf[siz++]=x%10; x/=10; } } for(i=siz-1;i>=0;i--) putchar(bf[i]+'0'); putchar('\n'); } }; namespace Tree{ int first[maxn]; int next[maxm],to[maxm],dist[maxm]; int tot=0; inline void Add_Edge(int u,int v,int d){ tot++; next[tot]=first[u]; first[u]=tot; to[tot]=v; dist[tot]=d; } int d[maxn],father[maxn]; vector<int> hx; void dfs_1(int x,int fa,int dis){ d[x]=dis; father[x]=fa; int i; GRAPH_REP(i,x){ if(to[i]!=fa) dfs_1(to[i],x,dis+dist[i]); } hx.push_back(x); } int lazy[maxn]; inline void ClearLazy(){ memset(lazy,0,sizeof(lazy)); } inline int DealCheck(int x){ register int i,v,ans=0; REP_B(i,n){ v=hx[i-1]; lazy[father[v]]+=lazy[v]; } REP_B(i,n){ if(lazy[i]==x){ ans=max(ans,d[i]-d[father[i]]); } } return ans; } // Djoin Set int p[maxn]; int find_set(int x){ if(p[x]==x) return x; else return p[x]=find_set(p[x]); } inline void union_set(int x,int y){ p[find_set(y)]=find_set(x); } inline void make_set(int x){ p[x]=x; } // LCA struct Query{ int u,v,b; }; bool vis[maxn]; vector<Query> qs; vector<int> querys[maxn]; inline void Add_Query(int x,int y,int b){ querys[x].push_back(qs.size()); qs.push_back((Query){x,y,b}); } int res[maxn][3]; int n; void LCA(int u){ make_set(u); vis[u]=true; int i,temp; REP(i,querys[u].size()){ Query& tep=qs[querys[u][i]]; if(vis[tep.v]) res[tep.b][2]=find_set(tep.v); } for(i=first[u];i;i=next[i]){ temp=to[i]; if(!vis[temp]){ LCA(temp); union_set(u,temp); } } } }; using namespace FastIO; struct Deal{ int u,v,lca,d; bool operator <(const Deal& x) const{ return d<x.d; } }; vector<Deal> V; int m; inline bool Check(int x){ register int i,ideal=0,yh=V[m-1].d; Tree::ClearLazy(); for(i=m-1;i>=0;i--){ int& u=V[i].u,v=V[i].v,lca=V[i].lca,d=V[i].d; if(d>x){ ideal++; Tree::lazy[u]++; Tree::lazy[v]++; Tree::lazy[lca]-=2; }else{ break; } } yh-=Tree::DealCheck(ideal); return yh<=x; } int main(){ register int i,u,v,lca,d; FastIO::InitFastIO(); n=readint(); m=readint(); REP(i,n-1){ u=readint(); v=readint(); d=readint(); Tree::Add_Edge(u,v,d); Tree::Add_Edge(v,u,d); } REP(i,m){ u=readint(); v=readint(); Tree::Add_Query(u,v,i); Tree::Add_Query(v,u,i); Tree::res[i][0]=u; Tree::res[i][1]=v; } Tree::dfs_1(1,0,0); Tree::LCA(1); REP(i,m){ u=Tree::res[i][0]; v=Tree::res[i][1]; lca=Tree::res[i][2]; d=Tree::d[u]+Tree::d[v]-2*Tree::d[lca]; V.push_back((Deal){u,v,lca,d}); } sort(V.begin(),V.end()); u=0; v=V[m-1].d+1; while(u<v){ d=u+(v-u)/2; if(Check(d)) v=d; else u=d+1; } putint(u); return 0; }
[洛谷 P2679]子串
DP神题……
第一眼都能看出来是DP,然后大约构思就出来了,但细节很复杂啊……
看完第一眼后大家大约都能想出来[tex]d[i][j][k][/tex]这样的状态,但是注意[tex]A[i]=B[j][/tex](字符数组细节此处未予考虑)的情况和整体情况要独立对待,否则这题只能233
然后下面就不难想了。但是注意直接开数组会233,要开滚动数组防MLE。
代码:
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn=1001,maxm=201,maxk=201; const int MOD=1e9+7; int d[2][maxm][maxk][2]; char A[maxn]; char B[maxm]; int main(){ register int i,j,p,now,pre; int n,m,k; scanf("%d%d%d",&n,&m,&k); scanf("%s%s",A,B); d[0][0][0][1]=1; for(i=1;i<=n;i++){ now=i%2; pre=1-now; d[now][0][0][1]=1; for(j=1;j<=m;j++){ if(A[i-1]==B[j-1]) for(p=1;p<=min(k,j);p++){ d[now][j][p][0]=(d[pre][j-1][p][0]+d[pre][j-1][p-1][1])%MOD; d[now][j][p][1]=(d[pre][j][p][1]+d[now][j][p][0])%MOD; } else for(p=1;p<=min(k,j);p++){ d[now][j][p][0]=0; d[now][j][p][1]=d[pre][j][p][1]; } } } printf("%d\n",d[n%2][m][k][1]); return 0; }
[BZOJ 4325]斗地主
是的你没有看错!就是这道坑提!
这题基本是个半成品,歧义真的,真的,真的很大。
至于方法?我们注意到同样的牌能一起出就尽可能避免拆开。例如,四带二分成炸弹加两单点(对子)肯定不好。三带一或者三带二也如此。先考虑不出顺子的最优解,并据此进行深度限制,再考虑出顺子。
然后,DFS暴搜即可。
顺便讲一下坑点:
- 顺子可以有A。
- 三带一,三带二,四代二都可以有头。但是大小头要区别开。
代码:
/************************************************************** Problem: 4325 User: danihao123 Language: C++ Result: Accepted Time:24 ms Memory:820 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n; int ans; int cnt[5],hand[15]; void dfs(int x){ if(x>ans) return; int i,j,rest=0; memset(cnt,0,sizeof(cnt)); for(i=0;i<=14;i++){ cnt[hand[i]]++; } while(cnt[4]>0){ cnt[4]--; rest++; if(cnt[2]>=2) cnt[2]-=2; else if(cnt[1]>=2) cnt[1]-=2; } while(cnt[3]>0){ cnt[3]--; rest++; if(cnt[2]>0) cnt[2]--; else if(cnt[1]>0) cnt[1]--; } if(hand[0] && hand[1] && cnt[1]>=2) rest--; ans=min(ans,cnt[1]+cnt[2]+rest+x); for(i=3;i<=10;i++){ for(j=i;j<=14 && hand[j];j++){ hand[j]--; if(j-i+1>=5) dfs(x+1); } while(j>i) hand[--j]++; } for(i=3;i<=12;i++){ for(j=i;j<=14 && hand[j]>=2;j++){ hand[j]-=2; if(j-i+1>=3) dfs(x+1); } while(j>i) hand[--j]+=2; } for(i=3;i<=13;i++){ for(j=i;j<=14 && hand[j]>=3;j++){ hand[j]-=3; if(j-i+1>=2) dfs(x+1); } while(j>i) hand[--j]+=3; } } int main(){ int T,u,v; register int i; scanf("%d%d",&T,&n); while(T--){ ans=n; memset(hand,0,sizeof(hand)); for(i=1;i<=n;i++){ scanf("%d%d",&u,&v); if(!u){ if(v==2) hand[1]++; else hand[0]++; }else{ if(u==1) hand[14]++; else hand[u]++; } } dfs(0); printf("%d\n",ans); } return 0; }
[洛谷 P1967]货车运输
倍增LCA经典题……
这题的做法就是求最大生成树,然后用倍增LCA法求瓶颈路……
注意一下,两点不连通时输出-1(其实这个用并查集判断不就行了……)!!!
代码: