Warning: session_start(): open(/tmp/sess_49027d4f540d226f9bab94c4694b62e5, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430
Writing /data/wiki/data/cache/d/de2edb2fcb553ea79b79c722a4e13dbc.captchaip failed

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:legal_string:组队训练比赛记录:contest6 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:legal_string:组队训练比赛记录:contest6

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:组队训练比赛记录:contest6 [2021/07/26 22:48]
lgwza [补题情况]
2020-2021:teams:legal_string:组队训练比赛记录:contest6 [2021/08/11 20:47] (当前版本)
jxm2001
行 4: 行 4:
  
 ^  题目 ​ ^  蒋贤蒙 ​ ^  王赵安 ​ ^  王智彪 ​ ^ ^  题目 ​ ^  蒋贤蒙 ​ ^  王赵安 ​ ^  王智彪 ​ ^
-|  A  |  ​ ​|  ​ ​| ​ 0  |  +|  A  |  ​ ​|  ​ ​| ​ 0  |  
-|  B  |  1  |  2  |  ​ |  +|  B  |  1  |  2  |  ​ |  
-|  C  |  1  |  ​ ​|  ​ |  +|  C  |  1  |  ​ ​|  ​ |  
-|  D  |  ​ ​| ​ 0  |  0  |  +|  D  |  ​ ​| ​ 0  |  0  |  
-|  E  |  2  |  0  |  ​ |  +|  E  |  2  |  0  |  ​ |  
-|  F  |  2  |  0  |  ​ |  +|  F  |  2  |  0  |  ​ |  
-|  G  |  ​ ​|  ​ ​| ​ 0  | +|  G  |  ​ ​|  ​ ​| ​ 0  | 
 |  H  |  0  |  0  |  0  |  |  H  |  0  |  0  |  0  | 
-|  I  |  2  |  0  |  ​ |  +|  I  |  2  |  0  |  ​ |  
-|  J  |  2  |  0  |  ​ ​| ​+|  J  |  2  |  0  |  ​ ​| ​
  
 ====== 题解 ====== ====== 题解 ======
 +
 +===== A. Guess and lies =====
 +
 +==== 题意 ====
 +
 +一开始有一个数 $x\in [1,​n]$,只有 $A$ 知道,$B$ 不知道。
 +
 +$B$ 每次可以给定一个 $y$,询问 $y\le x$ 是否成立。$A$ 至多可以说一次谎,$A$ 想要最大化询问次数,$B$ 想要最小化询问次数。
 +
 +假定 $B$ 第一次问的数为 $i$ 且 $A$ 回答 $i\le x$ 成立,问还需要询问多少次。
 +
 +==== 题解 ====
 +
 +假定 $B$ 询问的数为 $y$,如果 $A$ 回答 $i\le x$ 成立,则将 $[1,y)$ 的说谎次数 $+1$,否则将 $[y,n]$ 的说谎次数 $+1$。
 +
 +易知最后只剩下一个说谎次数不超过 $1$ 的数就是答案。于是 $B$ 相当于每个把 $[1,n]$ 分成两段,而 $A$ 需要选择一段 $+1$。
 +
 +不妨在每次操作后删去大于 $1$ 的值,于是不难发现剩下的序列一定满足 $11100011$ 的形式。
 +
 +假定当前序列开头有连续 $a$ 个 $1$,中间有连续 $b$ 个 $0$,最后有连续 $c$ 个 $1$。设 $\text{dp}(a,​b,​c)$ 表示当前局面剩下的操作次数。
 +
 +不难发现可以枚举分割的位置,然后计算答案,时间复杂度 $O\left(n^4\right)$。
 +
 +另外还有一个性质,设 $f(a,​b,​c,​k)$ 表示当前局面如果决策位置为 $k$ 则接下来还需要操作的次数,不难发现 $f(a,​b,​c,​k)$ 为单峰函数。
 +
 +同时,设当前局面的最优决策位置为 $p(a,​b,​c)$,有 $c_1\le c_2\to p(a,​b,​c_1)\le p(a,​b,​c_2)$。
 +
 +于是考虑决策单调性,可以做到 $O\left(n^3\right)$,但还是不能通过本题数据范围。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=505;
 +int dp[MAXN][MAXN][MAXN];​
 +int pos[MAXN][MAXN][MAXN];​
 +int cal(int a,int b,int c,int p){
 + if(p<​=a)
 + return max(dp[a-p][b][c],​dp[p+b][0][0]);​
 + else if(p<​=a+b){
 + int l=p-a,​r=a+b-p;​
 + return max(dp[a][l][r],​dp[l][r][c]);​
 + }
 + else{
 + int l=p-a-b,​r=a+b+c-p;​
 + return max(dp[a][b][l],​dp[b+r][0][0]);​
 + }
 +}
 +int main(){
 + int n=read_int();​
 + pos[1][0][0]=pos[0][1][0]=pos[0][0][1]=1;​
 + _rep(s,​2,​n){
 + for(int a=s;​a>​=0;​a--)_rep(b,​0,​s-a){
 + int c=s-a-b;
 + int &​p=pos[a][b][c];​
 + if(c==0)
 + p=1;
 + else
 + p=pos[a][b][c-1];​
 + while(p<​s-1){
 + if(cal(a,​b,​c,​p)>​=cal(a,​b,​c,​p+1))
 + p++;
 + else
 + break;
 + }
 + dp[a][b][c]=cal(a,​b,​c,​p)+1;​
 + }
 + }
 + _for(i,​0,​n)
 + space(dp[i][n-i][0]);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +不难发现 $B$ 询问的次数最多 $O\left(\log n\right)$ 次,于是交换 $\text{dp}$ 的值域和第三维。
 +
 +设 $\text{dp}(i,​a,​b)$ 表示 $i$ 次操作能处理的局面 $(a,b,c)$ 中 $c$ 的最大值,如果 $\text{dp}(i,​a,​b)=-1$ 表示 $i$ 次操作不能处理局面 $(a,​b,​0)$。
 +
 +关于状态转移也比较复杂,要分三种情况。
 +
 +如果 $k\in [1,​a]$,则 $(a,b,c)\to (k+b,​0,​0),​(a-k,​b,​c)$,此时要保证 $\text{dp}(i-1,​k+b,​0)\ge 0$ 才算合法,否则 $B$ 就不能 $i$ 步走完。
 +
 +合法情况下有 $\text{dp}(i,​a,​b)\gets \text{dp}(i-1,​a-k,​b)$。不难发现,$\text{dp}(i-1,​a-k,​b)$ 随 $k$ 增加递减,于是取合法情况的分界点即可。
 +
 +如果 $k\in (a,​a+b]$,则 $(a,b,c)\to (a,​k-a,​a+b-k),​(k-a,​a+b-k,​c)$,此时要保证 $\text{dp}(i-1,​a,​k-a)\ge a+b-k$ 才算合法。
 +
 +合法情况下有 $\text{dp}(i,​a,​b)\gets \text{dp}(i-1,​k-a,​a+b-k)$。同样 $\text{dp}(i-1,​k-a,​a+b-k)$ 也随 $k$ 增加递减。
 +
 +最后 $k\in (a+b,​a+b+c]$,不妨记 $k'​=k-a-b$,则 $(a,b,c)\to (b,​0,​c-k'​),​(a,​b,​k'​)$。
 +
 +此时要保证 $\text{dp}(i-1,​b,​0),​\text{dp}(i-1,​a,​b)\ge 0$ 才算合法,不难发现,此时 $c=c-k'​+k'​=\text{dp}(i-1,​b,​0)+\text{dp}(i-1,​a,​b)$。
 +
 +同时 $p(i,a,b)$ 表示当前状态最优决策位置,关于 $a$ 貌似具有不严格单峰性,根据打表结果有 $p(i,​a,​b)\ge p(i,​a-1,​b)-1$。
 +
 +于是可以 $O(n^2\log n)$ 通过此题。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXV=20,​MAXN=2005;​
 +int dp[MAXV][MAXN][MAXN];​
 +bool check(int i,int a,int b,int p){
 + if(p<​=a)
 + return dp[i-1][p+b][0]>​=0;​
 + else
 + return dp[i-1][a][p-a]>​=a+b-p;​
 +}
 +int main(){
 + int n=read_int();​
 + mem(dp,​-1);​
 + dp[0][0][0]=1;​
 + dp[0][1][0]=dp[0][0][1]=0;​
 + _for(i,​1,​MAXV){
 + _rep(b,​0,​n)for(int a=0,​p=0;​a<​=n-b;​a++){
 + while(p<​a+b&&​check(i,​a,​b,​p))p++;​
 + while(p&&​!check(i,​a,​b,​p))p--;​
 + dp[i][a][b]=(p<​=a)?​dp[i-1][a-p][b]:​dp[i-1][p-a][a+b-p];​
 + if(dp[i-1][a][b]>​=0&&​dp[i-1][b][0]>​=0)
 + dp[i][a][b]=max(dp[i][a][b],​dp[i-1][a][b]+dp[i-1][b][0]);​
 + }
 + }
 + _for(i,​0,​n){
 + _for(j,​0,​MAXV){
 + if(dp[j][i][n-i]>​=0){
 + space(j);​
 + break;
 + }
 + }
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +===== D. Count =====
 +
 +==== 题意 ====
 +
 +给定一个 $n\times n$ 的网格图,有 $k$ 个格子不能选,分别为 $(r_1,​c_1),​(r_2,​c_2)\cdots (r_k,​c_k)$。
 +
 +在此基础上选择 $m$ 个格子,满足每行每列至少选中一个格子,$r=c$ 和 $r+c=n+1$ 的对角线上至少选中一个格子。
 +
 +==== 题解 ====
 +
 +考虑容斥,首先枚举 $k$ 个不能选的格子的子集,强制选中这些格子,设共选了 $c$ 个格子。
 +
 +进一步容斥计算在强制选中这些格子对应的方案数,考虑是否禁止对角线、是否禁止某行某列。
 +
 +设 $\text{dp}(i,​j,​k)$ 表示强制只选 $i$ 行,只选 $j$ 列,且这些位置中有 $k$ 个位置因为强制不选对角线被禁止的方案数。
 +
 +于是状态 $(i,j,k)$ 对应的方案数为 ${i\times j-k-c \choose m-c}\text{dp}(i,​j,​k)$。接下来考虑如何计算 ​ $\text{dp}(i,​j,​k)$。
 +
 +事实上,只有形如第 $i$ 行、第 $i$ 列、第 $n+1-i$ 行、第 $n+1-i$ 列这样的四元组会对 $k$ 产生影响。
 +
 +于是可以将每个四元组看成一个物品,$O(2^4)$ 枚举这四元组每个元素的选择情况计算对 $k$ 的影响,然后三维背包合并所有四元组贡献。
 +
 +由于 $(i,j,k)$ 的每个维度上界都是 $O(n)$,所以总时间复杂 $O\left(2^kn^4\right)$。实际上还是比较卡的,但题目数据比较友好。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=35,​MAXV=MAXN*MAXN,​mod=1e4+7;​
 +int dp[MAXN][MAXN][MAXN<<​1],​temp[MAXN][MAXN][MAXN<<​1];​
 +int sr[MAXN],​sc[MAXN],​node[MAXN][2],​frac[MAXV],​invf[MAXV];​
 +int quick_pow(int n,int k){
 + int ans=1;
 + while(k){
 + if(k&​1)ans=ans*n%mod;​
 + n=n*n%mod;​
 + k>>​=1;​
 + }
 + return ans;
 +}
 +int C(int n,int m){
 + return frac[n]*invf[m]%mod*invf[n-m]%mod;​
 +}
 +int solve(int n,int _k,int _c,int _s){
 + int tf1=2,​tf2=2,​cnt=0;​
 + mem(sr,​0);​mem(sc,​0);​
 + _for(i,​0,​_k){
 + if(_s&​(1<<​i)){
 + sr[node[i][0]]=1,​sc[node[i][1]]=1;​
 + if(node[i][0]==node[i][1])
 + tf1=1;
 + if(node[i][0]+node[i][1]==n+1)
 + tf2=1;
 + cnt++;
 + }
 + }
 + int ans=0;
 + _for(f1,​0,​tf1)_for(f2,​0,​tf2){
 + mem(dp,​0);​
 + dp[0][0][0]=1;​
 + int mr=0,​mc=0,​mk=0;​
 + for(int p1=1,​p2=n;​p1<​=p2;​p1++,​p2--){
 + mem(temp,​0);​
 + if(p1!=p2){
 + mr+=2,​mc+=2,​mk+=4;​
 + _for(r1,​sr[p1],​2)_for(r2,​sr[p2],​2)
 + _for(c1,​sc[p1],​2)_for(c2,​sc[p2],​2){
 + int dr=r1+r2,​dc=c1+c2;​
 + int dk=(r1&​c1&​f1)+(r2&​c2&​f1)+(r1&​c2&​f2)+(r2&​c1&​f2);​
 + _rep(i,​dr,​mr)_rep(j,​dc,​mc)_rep(k,​dk,​mk)
 + temp[i][j][k]+=dp[i-dr][j-dc][k-dk];​
 + }
 + }
 + else{
 + mr++,​mc++,​mk++;​
 + _for(dr,​sr[p1],​2)_for(dc,​sc[p1],​2){
 + int dk=dr&​dc&​(f1|f2);​
 + _rep(i,​dr,​mr)_rep(j,​dc,​mc)_rep(k,​dk,​mk)
 + temp[i][j][k]+=dp[i-dr][j-dc][k-dk];​
 + }
 + }
 + _rep(i,​0,​mr)_rep(j,​0,​mc)_rep(k,​0,​mk)
 + dp[i][j][k]=temp[i][j][k]%mod;​
 + }
 + _for(i,​0,​MAXN)_for(j,​0,​MAXN)_for(k,​0,​MAXN<<​1){
 + int tot=i*j-k;
 + if(tot>​=_c){
 + int s=C(tot-cnt,​_c-cnt)*dp[i][j][k]%mod;​
 + int sg=(n-i)+(n-j)+f1+f2+cnt;​
 + if(sg&​1)
 + ans=(ans-s)%mod;​
 + else
 + ans=(ans+s)%mod;​
 + }
 + }
 + }
 + return (ans+mod)%mod;​
 +}
 +int main()
 +{
 + int n=read_int(),​k=read_int(),​c=read_int();​
 + _for(i,​0,​k)
 + node[i][0]=read_int(),​node[i][1]=read_int();​
 + frac[0]=1;
 + _for(i,​1,​MAXV)
 + frac[i]=frac[i-1]*i%mod;​
 + invf[MAXV-1]=quick_pow(frac[MAXV-1],​mod-2);​
 + for(int i=MAXV-1;​i;​i--)
 + invf[i-1]=invf[i]*i%mod;​
 + int ans=0;
 + _for(i,​0,​1<<​k)
 + ans=(ans+solve(n,​k,​c,​i))%mod;​
 + enter(ans);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +===== G. Yu Ling(Ling YueZheng) and Colorful Tree =====
 +
 +==== 题意 ====
 +
 +给定一棵点权树,初始值各点权值为 $0$,接下来两种操作:
 +
 +  - $w(u)\gets x$,保证 $w(u)$ 之前一定为 $0$,该类操作的 $x$ 都不相同且 $1\le x\le n$
 +  - 给定 $1\le l,r,x\le n$,查询 $u$ 的最近祖先节点 $v$,满足 $x\mid w(v),l\le w(v)\le r$,输出 $u,v$ 之间的距离
 +
 +==== 题解 ====
 +
 +考虑离线操作,对每个权值,维护对他有贡献的操作序列。
 +
 +首先对操作 $1$,显然 $x$ 会对所有是 $x$ 的因子的权值产生贡献,于是将操作 $1$ 加入到所有 $x$ 的因子的操作序列中。
 +
 +对操作 $2$,直接丢到权值 $x$ 的操作序列处理即可。
 +
 +接下来单独考虑每个权值的操作序列,发现操作序列一定都满足 $x\mid w(v)$ 这个约束。
 +
 +于是只需要考虑 $l\le w(v)\le r$ 和 $v$ 是 $u$ 的最近祖先这两个约束。
 +
 +首先转化一下$v$ 是 $u$ 的最近祖先这个约束,不难发现满足该约束的 $v$ 就是 $u$ 的祖先中 $\text{dfs}$ 序最大的点。
 +
 +维护一个二维矩阵 $C$,$C[x][y]$ 表示 $\text{dfs}$ 序最大的 $v$,满足 $w(v)=x$ 且 $w(v)$ 是节点 $y$ 的祖先。
 +
 +于是对操作 $1$ 等价于修改
 +
 +$$
 +C[x][\text{dfn}(u)\sim \text{dfn}(u)+\text{sz}(u)-1]\gets \text{dfn}(u)
 +$$
 +
 +操作 $2$ 等价于查询
 +
 +$$
 +\max\left(C[l\sim r][\text{dfn}(u)]\right)
 +$$
 +
 +考虑树套树维护 $C$,考虑修改操作共 $O(n\log n)$ 次,查询操作 $O(n)$,于是总时间复杂度 $O\left(n\log^3 n\right)$。
 +
 +关于空间复杂度,首先对每个权值,由于他的倍数最多只有 $O(n)$ 个,所以修改操作最大只有 $O(n)$ 次。
 +
 +树套树第二维是动态开点线段树,于是空间复杂度 $O\left(n\log^2 n\right)$。注意处理完每个权值的操作序列后要删除线段树,否则会爆空间。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1.2e5+5,​MAXM=150;​
 +struct Edge{
 + int to,w,next;
 +}edge[MAXN<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Insert(int u,int v,int w){
 + edge[++edge_cnt]=Edge{v,​w,​head[u]};​
 + head[u]=edge_cnt;​
 +}
 +int dfl[MAXN],​dfr[MAXN],​dfu[MAXN],​dfs_t;​
 +LL dis[MAXN];
 +void dfs(int u,int fa){
 + dfl[u]=++dfs_t;​
 + dfu[dfs_t]=u;​
 + for(int i=head[u];​i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(v==fa)continue;​
 + dis[v]=dis[u]+edge[i].w;​
 + dfs(v,u);
 + }
 + dfr[u]=dfs_t;​
 +}
 +struct node{
 + int u,x,l,r,id;
 + node(int u=0,int x=0,int l=0,int r=0,int id=0){
 + this->​u=u;​
 + this->​x=x;​
 + this->​l=l;​
 + this->​r=r;​
 + this->​id=id;​
 + }
 +};
 +namespace Tree{
 + int ch[MAXN*MAXM][2],​s[MAXN*MAXM],​cnt;​
 + void clear(){cnt=0;​}
 + void update(int &k,int vl,int vr,int ql,int qr,int v){
 + if(!k){
 + k=++cnt;
 + s[k]=0;
 + ch[k][0]=ch[k][1]=0;​
 + }
 + if(ql<​=vl&&​vr<​=qr){
 + s[k]=max(s[k],​v);​
 + return;
 + }
 + int vm=vl+vr>>​1;​
 + if(vm>​=ql)
 + update(ch[k][0],​vl,​vm,​ql,​qr,​v);​
 + if(vm<​qr)
 + update(ch[k][1],​vm+1,​vr,​ql,​qr,​v);​
 + }
 + int query(int k,int vl,int vr,int pos){
 + if(!k)return 0;
 + if(vl==vr)return s[k];
 + int vm=vl+vr>>​1;​
 + if(vm>​=pos)
 + return max(s[k],​query(ch[k][0],​vl,​vm,​pos));​
 + else
 + return max(s[k],​query(ch[k][1],​vm+1,​vr,​pos));​
 + }
 +}
 +int rt0[MAXN],​rt[MAXN],​n;​
 +#define lowbit(x) (x&​(-x))
 +void update(int pos,int u){
 + Tree::​update(rt0[pos],​1,​n,​dfl[u],​dfr[u],​dfl[u]);​
 + while(pos<​=n){
 + Tree::​update(rt[pos],​1,​n,​dfl[u],​dfr[u],​dfl[u]);​
 + pos+=lowbit(pos);​
 + }
 +}
 +int query(int l,int r,int u){
 + int ans=0;
 + while(l<​=r){
 + ans=max(ans,​Tree::​query(rt0[r],​1,​n,​dfl[u]));​
 + for(--r;​r-l>​=lowbit(r);​r-=lowbit(r))
 + ans=max(ans,​Tree::​query(rt[r],​1,​n,​dfl[u]));​
 + }
 + return ans;
 +}
 +void del(int pos){
 + rt0[pos]=0;​
 + while(pos<​=n){
 + rt[pos]=0;​
 + pos+=lowbit(pos);​
 + }
 +}
 +vector<​int>​ d[MAXN];
 +vector<​node>​ opt[MAXN];
 +LL ans[MAXN];
 +void solve(int v){
 + for(node q:opt[v]){
 + if(q.x==0){
 + int t=query(q.l,​q.r,​q.u);​
 + if(t==0)
 + ans[q.id]=-1;​
 + else
 + ans[q.id]=dis[q.u]-dis[dfu[t]];​
 + }
 + else
 + update(q.x,​q.u);​
 + }
 + for(node q:opt[v]){
 + if(q.x!=0)
 + del(q.x);
 + }
 + Tree::​clear();​
 +}
 +int main(){
 + n=read_int();​
 + int q=read_int();​
 + _for(i,​1,​n){
 + int u=read_int(),​v=read_int(),​w=read_int();​
 + Insert(u,​v,​w);​
 + Insert(v,​u,​w);​
 + }
 + dfs(1,0);
 + _rep(i,​1,​n){
 + for(int j=i;​j<​=n;​j+=i)
 + d[j].push_back(i);​
 + }
 + int qcnt=0;
 + while(q--){
 + int type=read_int(),​u=read_int();​
 + if(type==0){
 + int x=read_int();​
 + for(int v:d[x])
 + opt[v].push_back(node(u,​x));​
 + }
 + else{
 + int l=read_int(),​r=read_int(),​x=read_int();​
 + opt[x].push_back(node(u,​0,​l,​r,​qcnt++));​
 + }
 + }
 + _rep(i,​1,​n)
 + solve(i);
 + _for(i,​0,​qcnt){
 + if(ans[i]==-1)
 + puts("​Impossible!"​);​
 + else
 + enter(ans[i]);​
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
  
 ===== I. Kuriyama Mirai and Exclusive Or ===== ===== I. Kuriyama Mirai and Exclusive Or =====
行 102: 行 540:
 </​hidden>​ </​hidden>​
  
 +====== 赛后总结 =====
  
 +jxm:找操作中的不变量可能是解题的一种思路,比如这场的 $B$ 或者博弈论题。 $A$ 据说是 $\text{dp}$ 优化题,但没看懂题解,先咕了。
  
 +update:补完A了,感觉性质比较玄学。
2020-2021/teams/legal_string/组队训练比赛记录/contest6.1627310932.txt.gz · 最后更改: 2021/07/26 22:48 由 lgwza