HL省选冲刺

D-8

申请停课,ly说的还是很有道理的(虽然我也这么想),文化课什么的,早晚都得学,oi可能就学不了了(tcl),想做什么还是去做吧,也没什么不可能的(是吧,陈。 :)  )做了准备(虽然并不adequate,但是不尝试=100%GG,尝试=99%GG)有计划每天上午考试下午改题晚上改题。结果已经不重要了(学2年了快,挺有趣的)

D-7

停课D1,早自习了。上午各科老师问我干啥了,尴尬死。。。我以为ly说过了,给各位造成了不便。。。sorry。上午考试发现自己菜死了。sb题都不会了,analysis below

T1

 

 (其他样例啥的就不写了,子任务)

先分析一下,一开始觉得期望,就没怎么看,后来看到了子任务有 只有一个边有权值,暗示我们这个题只需要一个一个边考虑就行。首先对于一条边,左边x*boy,y*girl,(右边m-x*boy,m-y*girl)然后左边子树size,(右边子树n-size)我们肯定想最大,最大的话就是min(x,m-y)+min(y,m-x) 这东西画画图发现=max(x+y,2m-x-y)

 实际上问题就变成了,2m中选x个人(不分男女)然后瞎jb放

还有个点就是:根据调整法(我这么叫他)我们肯定能在保证一条边达到最大贡献 ,同时 ,让另一条边也达到最大 贡献,然后推广到所有边。

然后问题就愉快的变成了 sigma w[u,v]*c(2m,x)*size[u]^x*size[v]^(2m-x)*max(x,2m-x) (x=1~2m-1)瞎jb选x个人,瞎jb放在一边,然后瞎jb连边达到最大,然后就可以了。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 using namespace std;
 4 const int maxn=2505;
 5 const LL P=1e9+7ll;
 6 int Pow[maxn][maxn<<1];//pow[i][j]=i^j
 7 int sum[maxn];//sum[i]=sigma C(2m,j)i^j*(n-i)^(2m-j) (j=1~2m-1)
 8 int n,m;
 9 int w[maxn];
10 struct Edge{
11     int u,v;
12     Edge(int U=0,int V=0):u(U),v(V){}
13 }e[maxn];
14 int ans;
15 int fst[maxn],nxt[maxn<<1],to[maxn<<1],edge_count;
16 inline void add(int x,int y){
17     edge_count++;
18     to[edge_count]=y;
19     nxt[edge_count]=fst[x];
20     fst[x]=edge_count;
21 }
22 int size[maxn];
23 void dfs(int u,int fa){
24     size[u]=1;
25     for(int i=fst[u];i;i=nxt[i]){
26         int v=to[i];
27         if(v==fa)continue;
28         dfs(v,u);
29         size[u]+=size[v];
30     }
31 }
32 int jc[maxn<<1],inv[maxn<<1],njc[maxn<<1];
33 inline void com(){
34     inv[1]=jc[1]=njc[1]=jc[0]=njc[0]=1;
35     for(int i=2;i<=m<<1;i++){
36         jc[i]=1ll*jc[i-1]*i%P;
37         inv[i]=1ll*(P-P/i)*inv[P%i]%P;
38         njc[i]=1ll*njc[i-1]*inv[i]%P;
39     }
40 }
41 inline int c(int n,int m){return 1ll*jc[n]*njc[m]%P*njc[n-m]%P;}
42 inline void init(){
43     for(int i=1;i<n;i++){//ö¾Ù size & n-size 
44         Pow[i][0]=1;
45         for(int j=1;j<m<<1;j++)Pow[i][j]=1ll*Pow[i][j-1]*i%P;
46         //size^(x+y) & size^(2m-x-y)
47     }
48     for(int i=1;i<n;i++){//sum[size]=sigma size^(x+y)*(n-size)^(2m-x-y) (x+y = 1~2m-1)
49         for(int j=1;j<m<<1;j++)
50         sum[i]=(1ll*sum[i]+1ll*c(m<<1,j)*min(j,(m<<1)-j)%P*Pow[i][j]%P*Pow[n-i][(m<<1)-j]%P)%P;
51     }
52 }
53 int main(){
54     scanf("%d%d",&n,&m);
55     com();init();
56     for(int i=1,u,v;i<n;i++){
57         scanf("%d%d%d",&u,&v,&w[i]);
58         e[i]=Edge(u,v);
59         add(u,v);add(v,u);
60     }
61     dfs(1,0);
62     for(int i=1;i<n;i++){
63         ans=(1ll*ans+1ll*w[i]*sum[ min(size[e[i].u],size[e[i].v]) ])%P;
64     }
65     
66     printf("%d",ans);
67     return 0;
68 }
AC code

T2

挺sb的题,但是我想了好久,一开始发现,倍长(显然),枚举,判定,问题是如何判定,一直在想,如果 k 不合法,那么会在 相邻两个不相同的情况下 构成 f(k)个,相隔k个出现相同字符,(aaaaaaabaab,k=3(k的定义是,旋转的长度))然后想的是建边,跑topological sort,但是没办法,边长太多,不能直接搞。

经过qzh dalao启发,hash,冥思苦想 啊,hash判定字符串相等(重新推了一下 unsigned long long 自然溢出hash,真tm好用。),发现,刚刚自己一直想弄的,有f(k)个不合法子串<=>平移k位 重合。

 AC Code

 数位DP(入门~?)

以数位为阶段的DP。定义啥的上网找找,仅仅作为题集。

T1

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共有多少个 windy 数?
保证 1≤a≤b≤2×1091 \leq a \leq b \leq 2 \times 10^91ab2e9。
简单的dp,f[i][j]表示第i位 选了j的合法方案数,但是要注意前导零 的问题,和如果最高位到第i位都和b相同,那么i-1位就不能超过b的i-1位。
sb错误:记忆化的时候,如果最高位选0,那么就是 无限制&有前导零 ,但是记忆化状态的定义是 无限制&无前导零,不能使用。
code:
 1 #include<bits/stdc++.h>
 2 #define LL long long 
 3 using namespace std;
 4 LL a,b;
 5 LL f[50][20];//f[i][j]表示构成 第i位为j的方案数 
 6 LL sta[50],p;
 7 LL dfs(LL p,LL num,bool lim,bool zero){//表示上一位 为num,该位为 p ,是否有限制 是否连续为0 的方案数 
 8     if(!p)return !zero;//非零 & 确定每一位 
 9     if(!lim && !zero && f[p+1][num]!=-1)return f[p+1][num];
10     LL Max=(lim ? sta[p] : 9);LL ans=0;
11     for(LL i=0;i<=Max;i++){
12         if(abs(i-num)>=2 || zero)ans+=dfs(p-1,i,lim&&(i==sta[p]),zero&&(i==0));
13     }
14     if(!lim && !zero){
15         f[p+1][num]=ans;
16     }
17     return ans;
18 }
19 inline LL solve(LL n){
20     if(n<10)return n;
21     p=0;
22     while(n){
23         sta[++p]=n%10;
24         n/=10;
25     }
26     return dfs(p,0,1,1);
27 }
28 int main(){
29     scanf("%lld%lld",&a,&b);
30     memset(f,-1,sizeof f);
31     printf("%lld\n",solve(b)-solve(a-1));
32     return 0;
33 }
View Code
(qzh说记忆化很快(快是王道???希望qzh一直快下去  =_=||),比递推快,递推其实也好写吧。)

 T2~T?(later)

数据结构combination:

T1:套路题

给定一个长度为偶数的排列 p, 以及一个初始时为空的序列 q, 对其进行如下操作直 到 p 为空: 从 p 中找出两个相邻元素,按原来在 p 中的顺序加入 q 头部,然后把它们从 p 中 删去。 求可能得到的字典序最小的 q。

首先(这种题)可以显然的知道,如果一个数x在p奇数位置,那么它肯定可能成为q的首位,那么有个显然的结论就是,每次拿出的两个数,必定不会跨过x,所以把剩下的 序列分成两部分,右边的部分必须有一个(相对于新序列)奇数位的数y ,和x配对一起拿出来,然后又分裂成两个序列,所以我们可以用堆维护一下奇数位最小值 最小的 序列,用平衡树维护每个序列,(有个技巧就是,可以每次维护奇数位最小值 偶数位最小值 ,然后 记录一下这个序列是取奇数位还是偶数位)

ps:挺好的题,很久不写平衡树了(1h写,1h调)。

code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=2e5+5;
 4 int a[maxn],pos[maxn];
 5 struct Node{
 6     int val,num,ls,rs,rd,size,mn[2];//0->even(żÊý) , 1-> odd(ÆæÊý)
 7     Node(int Val=0,int Num=0,int Ls=0,int Rs=0,int Rd=0,int Size=0):val(Val),num(Num),ls(Ls),rs(Rs),rd(Rd),size(Size){}
 8 }t[maxn];
 9 inline void maintain(int x){
10     t[x].size=t[ t[x].ls ].size + t[ t[x].rs ].size+1;
11     t[x].mn[0]=min(t[ t[x].ls ].mn[0],t[ t[x].rs ].mn[0]);
12     t[x].mn[1]=min(t[ t[x].ls ].mn[1],t[ t[x].rs ].mn[1]);
13     t[x].mn[x&1]=min(t[x].mn[x&1],t[x].num);
14 }
15 void split(int x,int val,int &r1,int &r2){//[0,val] [val,inf]
16     if(!x){r1=r2=0;return;}
17     if(val>=t[x].val){
18         r1=x;
19         split(t[x].rs,val,t[x].rs,r2);
20     }
21     else{
22         r2=x;
23         split(t[x].ls,val,r1,t[x].ls);
24     }
25     maintain(x);
26 }
27 int merge(int r1,int r2){
28     if(!r1 || !r2)return r1+r2;
29     if(t[r1].rd < t[r2].rd){
30         t[r1].rs=merge(t[r1].rs,r2);
31         maintain(r1);
32         return r1;
33     }
34     else {
35         t[r2].ls=merge(r1,t[r2].ls);
36         maintain(r2);
37         return r2;
38     }
39 }
40 int n,root;
41 priority_queue< pair < int , int > ,vector < pair < int ,int > > ,greater< pair < int ,int > > > q;
42 bool vis[maxn];
43 inline void insert(int r){
44     int Min=t[r].mn[1 ^ vis[r]];
45     q.push(make_pair(Min,r));
46 }
47 inline void solve(int r){
48     q.pop();
49     int pm=pos[ t[r].mn[1^vis[r]] ];printf("%d ",a[pm]);
50     int r1,r2,r3,r4,r5,r6;
51     split(r,pm-1,r1,r2);
52     if(r1){
53         vis[r1]=vis[r];
54         insert(r1);
55     }
56     split(r2,pm,r2,r3);
57     vis[r3]=!vis[r];
58     pm=pos[ t[r3].mn[1^vis[r3]] ];printf("%d ",a[pm]);
59     split(r3,pm-1,r4,r5);
60     if(r4){
61         vis[r4]=vis[r3];
62         insert(r4);
63     }
64     split(r5,pm,r5,r6);
65     if(r6){
66         vis[r6]=!vis[r3];
67         insert(r6);
68     }
69 }
70 int main(){
71     srand(time(0));
72     scanf("%d",&n);
73     t[0].mn[0]=t[0].mn[1]=maxn;
74     for(int i=1;i<=n;i++){
75         scanf("%d",&a[i]);
76         t[i]=Node(i,a[i],0,0,rand(),1);
77         t[i].mn[0]=t[i].mn[1]=maxn;
78         t[i].mn[i&1]=a[i];
79         pos[a[i]]=i;
80         root=merge(root,i) ;
81     }
82     insert(root);
83     for(int i=1;i<=(n>>1);i++){
84         int rr=q.top().second;
85         solve(rr);
86     }
87     return 0;
88 }
AC Code

稍有思维:

T1:

小 B 有一个巨大的棋盘,你可以把它看作一个无限大的二维坐标系。

棋盘上有 N 个棋子, 每个棋子占一个格子,每个格子上至多只有一枚棋子。

小 B 还可以再在棋盘上下 K 个棋子,问有多少个不同的点,使得存在一种下法, 这个点可以成为所有棋子的对称中心 (对称中心可以不是格点)。

若这样的对称中心存在无穷多个,输出 −1。 棋子只能下在格点处。

这个题,qzh说挺水的。首先一个简单地思路就是枚举两个点,然后算出对称中心(x1+x2,y1+y2)就不/2了,然后枚举每个点判断是否有对称点,没有就记录,判断需要多少个新棋子

那么这个显然是超时的,既然k个新棋子可以消除k个当前棋子的影响,那么我们把所有的点按横坐标为第一关键字,纵坐标为第二关键字排序,左下k+1和右上k+1至少有一对匹配点。

枚举这对匹配点再进行判定就会减少很多复杂度(k<=20,k^2<=400)然后我一开始用了map套map暴力验证。实际上发现,排完序之后,点P[l]与点P[r]的对称中心,如果偏向 左边,那么r一定没有对称中心,反之亦然。

双指针判定一下就好。判定复杂度降为O(nlogn)->O(n)【ps:代码不粘了。自己应该想到的。】

 DP:

T1:「CQOI2017」老 C 的键盘

https://loj.ac/problem/3023

题意,给出一科完全儿叉树上父亲儿子的大小关系,问有多少种排列满足。

首先计数问题,应该想到dp,加上前几天做过的数位dp([ZJOI2010]排列计数https://www.luogu.com.cn/problem/P2606此题是满足堆性质的排列个数,因为一个堆,如果元素数量一定,不管元素是什么,构成方案数一定)本题我一开始设状态f[i][j]表示以i为根的子树 ,i选 数j 时的方案数,但是发现无法转移(因为谁也不知道其他的点放了啥,和堆还不一样)但是我们想这样一个问题(qzh):

对于这棵树,我们如果规定某种大小关系,求出拓扑序,那么这个拓扑序和树本身一一对应,或者说,和元素本身没关系,只和相对大小有关。所以我们仍然设f[i][j]为状态,但是不同的是:f[i][j]表示以i为根的子树,i在子树内rank 为j的方案数,巧妙地利用了刚刚的性质,还方便转移。所以我们直接树dp,每次枚举rank,枚举当前儿子会对rank产生多少贡献,最后利用题目限制条件,进行转移。由于n很小所以不用前缀or后缀和也是可以的。(据说枚举子树大小是O(logn)的)

code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int P=1e9+7;
 4 const int maxn=1e2+5;
 5 int c[maxn][maxn],f[maxn][maxn],size[maxn],temp[maxn][maxn];
 6 //f[i][j]±íʾÔÚµãiµÄ×ÓÊ÷ÖУ¬iµÄrankΪj
 7 int n;
 8 char ch[maxn];
 9 void dfs(int u){
10     size[u]=1;
11     f[u][1]=1;
12     for(int v=(u<<1);v<=min(n,u<<1|1);v++){
13         dfs(v);
14         size[u]+=size[v];
15         for(int j=1;j<=size[u];j++)
16         for(int k=0;k<=j;k++){
17             if(ch[v]=='<'){
18                 for(int l=k+1;l<=size[v];l++)
19                 temp[u][j]=(temp[u][j]+1ll*c[j-1][k]*c[size[u]-j][size[v]-k]%P*f[v][l]%P*f[u][j-k]%P)%P;
20             }
21             else {
22                 for(int l=0;l<=k;l++)
23                 temp[u][j]=(temp[u][j]+1ll*c[j-1][k]*c[size[u]-j][size[v]-k]%P*f[v][l]%P*f[u][j-k]%P)%P;
24             }
25         }
26         for(int j=1;j<=size[u];j++){
27             f[u][j]=temp[u][j];
28             temp[u][j]=0;
29         }
30     }
31 }
32 inline void init(){
33     for(int i=0;i<=maxn-5;i++)c[i][0]=1;
34     for(int i=1;i<=maxn-5;i++)for(int j=1;j<=i;j++)
35     c[i][j]=(c[i-1][j]+c[i-1][j-1])%P;
36 }
37 int main(){
38     scanf("%d%s",&n,ch+2);
39     init();
40     dfs(1);
41     int ans=0;
42     for(int i=1;i<=n;i++)ans=(ans+f[1][i])%P;
43     printf("%d",ans);
44     return 0;
45 }
AC Code

T2:

 

posted @ 2020-06-13 20:29  Tj1  阅读(228)  评论(0编辑  收藏  举报