2020.11.30~2020.12.6 做题记录

P1552 [APIO2012]派遣(线段树合并+二分)

线段树合并板子题,维护子树内薪水为i的忍者有多少个,并记录薪水的和。为了使能被派遣的忍者更多,贪心取子树内尽可能多的便宜忍者,线段树上二分即可

直接线段树合并可能会卡空间,离散化一下

线段树合并的最后一层不能pushup!!!会变成0的,因此需要边合并边求和

  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
",ans);
104     return 0;
105 }
View Code

 

#273 DNA序列 (DP)

设计$DP$,$f[i][j]$表示第一个串匹配到第i位,第二个串匹配到j位时的最大匹配值

然而连续空格的负贡献会减少,容易想到分三种情况讨论,所以加一维记录

$f[i][j][0/1/2]$分别表示两个串分别匹配到i,j时,最后一个位置是 x x // x _ // _ x 时的答案 (x是字母,_是空格)

转移方程很水,注意得赋初始值,但还不能直接memset,会T..

 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
",ans);
64     return 0;
65 }
View Code

发现如果只是经停某个点的话,在这个点$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]$

只能由满意度小的位置转移到满意度大的位置,所以需要根据满意度的值来确定枚举顺序

 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
",ans);
83     return 0;
84 }
View Code

神仙计数,想不出qwq,只能看题解了

题意:一行$n$个数,初始时全部为0。 需要进行$K$次操作,第$i$次操作为指定一个非空区间$[Li,Ri]$,将这个区间中的数全部+1。需要保证$K$次操作后每个数都不大于$T$。 求有多少种选择区间的方案。 两种方案不同当且仅当存在一个操作,在两种方案中指定的区间不同。$n,K,Tle 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})$

仔细想想,这种计数方法能规避多次操作相同区间造成的重复方案,同时保证相同起点/终点的区间贡献都是正确的

 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
",f[n][0][K]);
40     return 0;
41 }
View Code

P6932 [ICPC2017 WF]Money for Nothing(决策单调+分治)

原题解

首先考虑贪心,进货尽可能时间早,出货尽可能时间晚。

以时间为x轴,价格为y轴。按时间排序并筛除那些不优秀的点之后,容易发现进货和出货都是单调递减的离散函数。——感性理解一下,进货出货都一样,时间更早的价格更高,时间更晚的价格更低,才能赚的最多

推一下决策单调,假设当前进货商a的最佳出货商是b,那么a+1的决策位置一定是b或b右面的商家

最优决策下,每个进货点都对应某个或某些的出货点,放到坐标系里发现是按顺序排列的点对

怎么利用这个性质?分治!

找出mid的决策位置t,那么[l,mid-1]和[mid+1,r]的决策位置一定在t两侧,递归处理即可

 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
",ans);
76     return 0;
77 }
View Code

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还需要进入另一个子树头

 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
",ans);
71         e.clr(n);
72     }
73     return 0;
74 }
View Code
原文地址:https://www.cnblogs.com/guapisolo/p/14078292.html