2020.11.30~2020.12.6 做题记录
P1552 [APIO2012]派遣(线段树合并+二分)
线段树合并板子题,维护子树内薪水为i的忍者有多少个,并记录薪水的和。为了使能被派遣的忍者更多,贪心取子树内尽可能多的便宜忍者,线段树上二分即可
直接线段树合并可能会卡空间,离散化一下
线段树合并的最后一层不能pushup!!!会变成0的,因此需要边合并边求和
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 100005 6 #define M1 N1*50 7 #define ll long long 8 using namespace std; 9 10 int n,S,tot,maxn; 11 int real[N1]; 12 struct node{ 13 int id,val; 14 bool operator < (const node &s2) const 15 { 16 if(val!=s2.val) return val<s2.val; 17 return id<s2.id; 18 } 19 }a[N1]; 20 21 struct Edge{ 22 int to[N1],nxt[N1],head[N1],cte; 23 void ae(int u,int v) 24 { 25 cte++; to[cte]=v, nxt[cte]=head[u]; 26 head[u]=cte; 27 } 28 }e; 29 struct SEGTREE{ 30 int ls[M1],rs[M1],sum[M1],root[N1],cnt; 31 ll cost[M1]; 32 void pushup(int rt) 33 { 34 sum[rt]=sum[ls[rt]]+sum[rs[rt]]; 35 cost[rt]=cost[ls[rt]]+cost[rs[rt]]; 36 } 37 void upd(int x,int l,int r,int &rt,int w) 38 { 39 if(!rt) rt=++cnt; 40 sum[rt]+=w, cost[rt]+=real[x]; 41 if(l==r) return; 42 // if(l==r) { sum[rt]+=w, cost[rt]+=x; return; } 43 int mid=(l+r)>>1; 44 if(x<=mid) upd(x,l,mid,ls[rt],w); 45 else upd(x,mid+1,r,rs[rt],w); 46 pushup(rt); 47 } 48 int mrg(int r1,int r2) 49 { 50 if(!r1||!r2) return r1+r2; 51 int rt=++cnt; 52 sum[rt]=sum[r1]+sum[r2]; 53 cost[rt]=cost[r1]+cost[r2]; 54 ls[rt]=mrg(ls[r1],ls[r2]); 55 rs[rt]=mrg(rs[r1],rs[r2]); 56 // pushup(rt); 57 return rt; 58 } 59 ll query(ll res,int l,int r,int rt) 60 { 61 if(res>=cost[rt]) return sum[rt]; 62 if(l==r) return 0; 63 int mid=(l+r)>>1; 64 if(res>=cost[ls[rt]]) return sum[ls[rt]]+query(res-cost[ls[rt]],mid+1,r,rs[rt]); 65 else return query(res,l,mid,ls[rt]); 66 } 67 }s; 68 69 int fa[N1],sal[N1],lea[N1]; 70 71 ll dfs(int u) 72 { 73 int j,v; ll ans=0; 74 s.upd(sal[u],1,maxn,s.root[u],1); 75 for(j=e.head[u];j;j=e.nxt[j]) 76 { 77 v=e.to[j]; if(v==fa[u]) continue; 78 ans=max(ans,dfs(v)); 79 s.root[u]=s.mrg(s.root[u],s.root[v]); 80 } 81 ans=max(ans,1ll*lea[u]*s.query(tot,1,maxn,s.root[u])); 82 return ans; 83 } 84 int main() 85 { 86 // freopen("a.txt","r",stdin); 87 scanf("%d%d",&n,&tot); 88 for(int i=1;i<=n;i++) 89 { 90 scanf("%d%d%d",&fa[i],&sal[i],&lea[i]); 91 if(fa[i]) e.ae(fa[i],i); 92 else S=i; 93 a[i]=(node){i,sal[i]}; 94 } 95 sort(a+1,a+n+1); 96 maxn=1; 97 for(int i=1;i<=n;i++) 98 { 99 if(a[i].val!=a[i-1].val) maxn++, real[maxn]=a[i].val; 100 sal[a[i].id]=maxn; 101 } 102 ll ans=dfs(S); 103 printf("%lld\n",ans); 104 return 0; 105 }
#273 DNA序列 (DP)
设计$DP$,$f[i][j]$表示第一个串匹配到第i位,第二个串匹配到j位时的最大匹配值
然而连续空格的负贡献会减少,容易想到分三种情况讨论,所以加一维记录
$f[i][j][0/1/2]$分别表示两个串分别匹配到i,j时,最后一个位置是 x x // x _ // _ x 时的答案 (x是字母,_是空格)
转移方程很水,注意得赋初始值,但还不能直接memset,会T..
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 3040 6 #define inf 0x3f3f3f3f 7 #define ll long long 8 using namespace std; 9 10 char str1[N1],str2[N1]; 11 int a[N1],b[N1],w[4][4],f[N1][N1][3]; 12 int n,m,A,B; 13 int idx(char c) 14 { 15 if(c=='A') return 0; 16 if(c=='T') return 1; 17 if(c=='G') return 2; 18 if(c=='C') return 3; 19 return -1; 20 } 21 22 int main() 23 { 24 // freopen("a.txt","r",stdin); 25 int i,j,k; 26 scanf("%s",str1+1); n=strlen(str1+1); 27 for(i=1;i<=n;i++) a[i]=idx(str1[i]); 28 scanf("%s",str2+1); m=strlen(str2+1); 29 for(i=1;i<=m;i++) b[i]=idx(str2[i]); 30 for(i=0;i<4;i++) for(j=0;j<4;j++) scanf("%d",&w[i][j]); 31 scanf("%d%d",&A,&B); 32 //memset(f,-0x3f,sizeof(f)); 33 f[0][0][0]=0; f[0][0][1]=f[0][0][2]=-inf; 34 for(i=0;i<=n;i++) 35 { 36 for(j=0;j<=m;j++) 37 { 38 if(!i&&!j) continue; 39 f[i][j][0]=f[i][j][1]=f[i][j][2]=-inf; 40 if(i>0 && j>0) 41 for(k=0;k<=2;k++) //x x 42 f[i][j][0]=max(f[i][j][0], f[i-1][j-1][k]+w[a[i]][b[j]] ); 43 44 //x _ 45 if(i>0) 46 { 47 f[i][j][1]=max(f[i][j][1], f[i-1][j][0]-A ); 48 f[i][j][1]=max(f[i][j][1], f[i-1][j][1]-B ); 49 f[i][j][1]=max(f[i][j][1], f[i-1][j][2]-A ); 50 } 51 52 //_ x 53 if(j>0) 54 { 55 f[i][j][2]=max(f[i][j][2], f[i][j-1][0]-A ); 56 f[i][j][2]=max(f[i][j][2], f[i][j-1][1]-A ); 57 f[i][j][2]=max(f[i][j][2], f[i][j-1][2]-B ); 58 } 59 } 60 } 61 int ans=-inf; 62 ans=max(ans,f[n][m][0]); ans=max(ans,f[n][m][1]); ans=max(ans,f[n][m][2]); 63 printf("%d\n",ans); 64 return 0; 65 }
#274 旅游景点 (DP)
发现如果只是经停某个点的话,在这个点$DP$状态会很难设计。
实际上我们只考虑$DP$游览的位置就行了,中间走哪条路?当然是最短路
$n$比较小,$floyd$即可处理出任意两点间最短路长度
定义$f[i][t]$表示游览第i个点,经过的总时长为t时的最大满意度
容易得到$f[j][t+dis(i,j)+cost[j]]=min(f[i][t])+satisfy[j]$,且$sat[j]>sat[i]$
只能由满意度小的位置转移到满意度大的位置,所以需要根据满意度的值来确定枚举顺序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <queue> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define N1 105 7 #define T1 305 8 #define M1 10050 9 #define inf 0x3f3f3f3f 10 #define ll long long 11 using namespace std; 12 13 int n,m,S,T,E; 14 struct Edge{ 15 int to[M1],nxt[M1],len[M1],cte; int head[N1]; 16 void ae(int u,int v,int w) 17 { 18 cte++; to[cte]=v; nxt[cte]=head[u]; 19 head[u]=cte; len[cte]=w; 20 } 21 }e; 22 23 int dis[N1][N1],used[N1],cost[N1],sat[N1]; 24 int f[N1][T1],id[N1]; 25 int cmp(int x,int y){ return sat[x]<sat[y]; } 26 void floyd() 27 { 28 int i,j,k; 29 for(i=0;i<n;i++) dis[i][i]=0; 30 for(k=0;k<n;k++) 31 { 32 for(i=0;i<n;i++) 33 { 34 for(j=0;j<n;j++) 35 { 36 if(i==j) continue; 37 dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); 38 } 39 } 40 } 41 } 42 43 44 int main() 45 { 46 freopen("a.txt","r",stdin); 47 int i,j,k,x,y,w,ans; 48 scanf("%d%d%d%d%d",&n,&m,&T,&S,&E); 49 for(i=0;i<n;i++) scanf("%d",&cost[i]); 50 for(i=0;i<n;i++) scanf("%d",&sat[i]); 51 memset(dis,0x3f,sizeof(dis)); 52 for(i=1;i<=m;i++) 53 { 54 scanf("%d%d%d",&x,&y,&w); 55 e.ae(x,y,w), e.ae(y,x,w); 56 dis[x][y]=min(dis[x][y],w); dis[y][x]=min(dis[y][x],w); 57 } 58 floyd(); 59 for(i=0;i<n;i++) id[i]=i; 60 sort(id,id+n,cmp); 61 memset(f,-0x3f,sizeof(f)); 62 for(i=0;i<n;i++) if(dis[S][i]+cost[i]<=T) f[i][dis[S][i]+cost[i]]=sat[i]; //S->i 63 for(x=0;x<n;x++) //i->j 64 { 65 i=id[x]; 66 for(j=0;j<n;j++) if(sat[j]>sat[i]) 67 { 68 for(k=0;k+dis[i][j]+cost[j]<=T;k++) 69 { 70 f[j][k+dis[i][j]+cost[j]]=max(f[j][k+dis[i][j]+cost[j]], f[i][k]+sat[j]); 71 } 72 } 73 } 74 ans=0; 75 for(j=0;j<n;j++) //j->T 76 { 77 for(k=0;k+dis[j][E]<=T;k++) 78 { 79 ans=max(f[j][k],ans); 80 } 81 } 82 printf("%d\n",ans); 83 return 0; 84 }
#188 容器/区间 (区间DP)
神仙计数,想不出qwq,只能看题解了
题意:一行$n$个数,初始时全部为0。 需要进行$K$次操作,第$i$次操作为指定一个非空区间$[Li,Ri]$,将这个区间中的数全部+1。需要保证$K$次操作后每个数都不大于$T$。 求有多少种选择区间的方案。 两种方案不同当且仅当存在一个操作,在两种方案中指定的区间不同。$n,K,T\le 40$
考虑把统计一种方案,转换成从左向右扫描每个位置。扫到左端点=添加区间。扫到右端点=结束区间。
定义$f[i][j][k]$表示当前枚举到第$i$位,有$j$个区间还未结束,总计添加了$k$个区间的方案数。容易发现,我们还要枚举在第$i$个位置有$end$个区间结束,在第$i+1$个位置有sta个区间开始。得到转移方程:
$f[i+1][j-end+sta][k+sta]+=f[i][j][i]C_{j}^{end}C_{k+sta}^{sta}$
$C_{k+sta}^{sta}$表示把新加的$sta$个区间和原来的$k$个区间排列,由于新加的$sta$个区间是相同的(目前来看),所以用组合数而不用排列数
$C_{j}^{end}$表示从$j$个未结束区间中选择$end$个结束掉,在添加区间的过程中已经乘过排列区间的贡献,所以用组合数
时间复杂度$O(nK^{4})$
仔细想想,这种计数方法能规避多次操作相同区间造成的重复方案,同时保证相同起点/终点的区间贡献都是正确的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <queue> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define N1 42 7 #define inf 0x3f3f3f3f 8 #define ll long long 9 #define p 1011110011 10 using namespace std; 11 12 int n,K,T; 13 ll f[N1][N1][N1],mul[N1],inv[N1],minv[N1]; 14 ll C(int x,int y){ if(y>x) return 0; return mul[x]*minv[y]%p*minv[x-y]%p; } 15 void pre() 16 { 17 mul[0]=mul[1]=inv[0]=inv[1]=minv[0]=minv[1]=1; 18 for(int i=2;i<=K;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p; 19 for(int i=2;i<=K;i++) mul[i]=mul[i-1]*i%p, minv[i]=minv[i-1]*inv[i]%p; 20 for(int j=0;j<=T;j++) f[0][j][j]=1; //pre 21 } 22 23 int main() 24 { 25 // freopen("a.txt","r",stdin); 26 scanf("%d%d%d",&n,&K,&T); 27 int i,j,k,end,sta; 28 pre(); 29 for(i=0;i<n;i++) 30 for(j=0;j<=T;j++) //remain 31 for(k=0;k<=K;k++) //ends 32 { 33 for(end=0;end<=j;end++) 34 for(sta=0;k+sta<=K;sta++) 35 { 36 (f[i+1][j-end+sta][k+sta]+=f[i][j][k]*C(j,end)%p*C(k+sta,sta)%p)%=p; 37 } 38 } 39 printf("%d\n",f[n][0][K]); 40 return 0; 41 }
P6932 [ICPC2017 WF]Money for Nothing(决策单调+分治)
首先考虑贪心,进货尽可能时间早,出货尽可能时间晚。
以时间为x轴,价格为y轴。按时间排序并筛除那些不优秀的点之后,容易发现进货和出货都是单调递减的离散函数。——感性理解一下,进货出货都一样,时间更早的价格更高,时间更晚的价格更低,才能赚的最多
推一下决策单调,假设当前进货商a的最佳出货商是b,那么a+1的决策位置一定是b或b右面的商家
最优决策下,每个进货点都对应某个或某些的出货点,放到坐标系里发现是按顺序排列的点对
怎么利用这个性质?分治!
找出mid的决策位置t,那么[l,mid-1]和[mid+1,r]的决策位置一定在t两侧,递归处理即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <queue> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define N1 500005 7 #define inf 0x3f3f3f3f 8 #define ll long long 9 using namespace std; 10 11 const ll linf=1e18; 12 struct node{ 13 int x,y; 14 }su[N1],de[N1]; 15 int cmp1(node a,node b) 16 { 17 if(a.x!=b.x) return a.x<b.x; 18 return a.y>b.y; 19 } 20 int cmp2(node a,node b) 21 { 22 if(a.x!=b.x) return a.x<b.x; 23 return a.y<b.y; 24 } 25 26 int m,n,mm,nn; 27 int d[N1],p[N1],e[N1],q[N1]; 28 void del() 29 { 30 int i,j,mi; 31 for(i=1,mi=inf;i<=m;i++) 32 { 33 if(su[i].x==su[i-1].x || su[i].y>=mi) continue; 34 mm++; d[mm]=su[i].x, p[mm]=su[i].y, mi=su[i].y; 35 } 36 for(i=n,mi=0;i>=1;i--) 37 { 38 if(de[i].x==de[i+1].x || de[i].y<=mi) continue; 39 nn++; e[nn]=de[i].x, q[nn]=de[i].y, mi=de[i].y; 40 } 41 for(i=1;i<=nn;i++) if(i<nn-i+1) 42 swap(e[i],e[nn-i+1]), swap(q[i],q[nn-i+1]); 43 } 44 ll query(int i,int j) 45 { 46 if(e[j]<=d[i]) return -linf; 47 return 1ll*(e[j]-d[i])*(q[j]-p[i]); 48 } 49 ll solve(int l1,int r1,int l2,int r2) 50 { 51 int mid=(l1+r1)>>1,i,st=-1,ed=-1; ll ma=-linf,ans=0,tmp; 52 for(i=l2;i<=r2;i++) 53 { 54 tmp=query(mid,i); 55 if(tmp>ma) ma=tmp,st=ed=i; 56 else if(tmp==ma) ed=i; 57 } 58 ans=max(ans,ma); 59 if(l1<mid) tmp=solve(l1,mid-1,l2,st), ans=max(ans,tmp); 60 if(mid<r1) tmp=solve(mid+1,r1,ed,r2), ans=max(ans,tmp); 61 return ans; 62 } 63 64 int main() 65 { 66 // freopen("a.in","r",stdin); 67 scanf("%d%d",&m,&n); 68 for(int i=1;i<=m;i++) scanf("%d%d",&su[i].y,&su[i].x); 69 for(int i=1;i<=n;i++) scanf("%d%d",&de[i].y,&de[i].x); 70 sort(su+1,su+m+1,cmp2); sort(de+1,de+n+1,cmp2); 71 del(); 72 ll ans=solve(1,mm,1,nn); 73 for(int i=1;i<=mm;i++) if(d[i]==3 && p[i]==5) 74 n=n; 75 printf("%lld\n",ans); 76 return 0; 77 }
CF1453E Dog Snacks(树上构造)
比赛的时候思路对了,但时间不够,就没敲
下考和dkr交流,发现似乎是水题。今天改题敲30min就过了
定义$f[x]$表示狗狗吃掉$x$子树内最后一个零食时距离$x$的距离最小值
有这样一个贪心结论:
对于每个节点,狗狗离开$x$子树的时候一定从x子树内“最浅的地方”离开,能使得所需的$k$最小。这个“最浅的地方”有多浅是个根据$x$相邻的子节点的子树情况而定的,并不一定是距离$x$最近的末端节点(可以归纳想象一下)
那么有$f[x]=min(f[v]+1)$,$f[x]=0(x:endpoint)$
而节点$x$子树的答案则是$max(f[v]+2)$,不仅要从$v$跑到$x$,还需要进入x另一个子树的头,所以+2
注意1号节点答案可能有区别,$f[v]$最深的位置我们可以最后走,这样回来的时候跑到1就行了。容易发现这种情况和最大的f[v]数量有关,如果是一个,刚才的策略成立,如果是两个或更多,就不可行了,总得有一个v还需要进入另一个子树头
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <queue> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define N1 200010 7 #define M1 2*N1 8 #define inf 0x3f3f3f3f 9 #define ll long long 10 using namespace std; 11 12 const ll linf=1e18; 13 struct Edge{ 14 int to[M1],nxt[M1],head[N1],cte; 15 void ae(int u,int v) 16 { 17 cte++; to[cte]=v, nxt[cte]=head[u]; 18 head[u]=cte; 19 } 20 void clr(int n) 21 { 22 for(int i=1;i<=n;i++) head[i]=0; 23 for(int i=1;i<=cte;i++) to[cte]=nxt[cte]=0; 24 cte=0; 25 } 26 }e; 27 28 int T; 29 int n; 30 int sha[N1],mi[N1]; 31 void dfs(int u,int fa) 32 { 33 int j,v; 34 sha[u]=mi[u]=0; 35 for(j=e.head[u];j;j=e.nxt[j]) 36 { 37 v=e.to[j]; if(v==fa) continue; 38 dfs(v,u); 39 if(!sha[u]) sha[u]=inf; 40 sha[u]=min(sha[u],sha[v]+1); 41 mi[u]=max(mi[u],sha[v]+1); 42 } 43 mi[u]++; //折回来一下,末端点可忽略 44 if(u==1){ 45 int cnt=0; mi[u]=0; 46 for(j=e.head[u];j;j=e.nxt[j]) 47 { 48 v=e.to[j]; 49 if(sha[v]+1>mi[u]) mi[u]=sha[v]+1, cnt=1; 50 else if(sha[v]+1==mi[u]) cnt++; 51 } 52 if(cnt>1) mi[u]++; 53 } 54 } 55 56 int main() 57 { 58 scanf("%d",&T); 59 while(T--) 60 { 61 scanf("%d",&n); 62 int x,y,ans=0; 63 for(int i=1;i<n;i++) 64 { 65 scanf("%d%d",&x,&y); 66 e.ae(x,y), e.ae(y,x); 67 } 68 dfs(1,-1); 69 for(int i=1;i<=n;i++) ans=max(ans,mi[i]); 70 printf("%d\n",ans); 71 e.clr(n); 72 } 73 return 0; 74 }