长乐集训 Day1
A.divide
题意
给定正整数 $n,m$,构造方案使得 $m$ 分为 $n$ 个正整数的和,使得这些正整数的最小值大于它们的异或和。
Solution
如果 $n=1$ 显然无解。
如果 $n=2$:
-
$m$ 为偶数,直接构造 $\frac{m}{2},\frac{m}{2}$ 就好了。
-
$m$ 为奇数,当 $m\ne 2^k-1$ 时候构造 $\lfloor\frac{m}{2}\rfloor,\lfloor\frac{m}{2}\rfloor+1$ ,否则无解。
然后不会做了。以为 $3\le n \le 10^5$ 部分是神仙 dp 题,然后题解告诉我是傻逼大分讨,找规律写完就好了,让最小值为 $1$ 或者 $2$ 然后构造异或值为 $0$ 或者 $1$。
Code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
void solve(int n,int m)
{
if(n==1)
{
printf("-1\n");
return ;
}
else if(n==2)
{
if(m%2==0)
printf("%d %d\n",m/2,m/2);
else
{
int a=m/2,b=m-m/2;
if((a^b)>=std::min(a,b))
printf("-1\n");
else
printf("%d %d\n",a,b);
}
return ;
}
else if(n==3)
{
int high=(1<<(int)log2(m));
if(m%2==0)
{
if(m==2||m==4||m==8)
printf("-1\n");
else if(high==m)
printf("%d %d %d\n",3*(m/16),6*(m/16),7*(m/16));
else
printf("%d %d %d\n",m/2,(m-high)/2,high/2);
}
else
{
int a=m/2,b=(m-high)/2,c=high/2+1;
if(m==high+1||m==high+3)
{
if(m<=19)
printf("-1\n");
else if(m==high+1)
printf("%d %d %d\n",3*(int)(m/16)+1,6*(int)(m/16),7*(int)(m/16));
else if(m==high+3)
printf("%d %d %d\n",3*(int)(m/16)+1,6*(int)(m/16)+1,7*(int)(m/16)+1);
}
else if((a^b^c)>=std::min(a,std::min(b,c)))
printf("-1\n");
else
printf("%d %d %d\n",a,b,c);
// printf("%d %d %d\n",(int)(m/2),(int)(m-high)/2,(int)(high/2)+1);
}
return ;
}
else if(n==4)
{
if(m%2==0)
printf("%d %d 1 1\n",int(m/2)-1,int(m/2)-1);
else
{
if(m<=7)
printf("-1\n");
else
printf("%d %d 2 3\n",(m-5)/2,(m-5)/2);
}
return ;
}
else if(n==5)
{
if(m%2==0)
{
if(m==6)
printf("-1\n");
else
printf("%d %d 1 2 3\n",(int)(m/2)-3,(int)(m/2)-3);
}
else
{
if(m<=15)
printf("-1\n");
else
printf("%d %d 2 5 6\n",(int)(m-13)/2,(int)(m-13)/2);
}
return ;
}
else if(n%2==0)
{
if(m%2==0)
{
for(int i=1;i<=n-4;i++)
printf("1 ");
solve(4,m-n+4);
}
else
{
if(m<=2*n-1)
printf("-1\n");
else
{
for(int i=1;i<=n-4;i++)
printf("2 ");
solve(4,m-n*2+8);
}
}
return ;
}
else
{
if(m%2==0)
{
if(m==n+1)
printf("-1\n");
else
{
for(int i=1;i<=n-5;i++)
printf("1 ");
solve(5,m-n+5);
}
}
else
{
if(m<=2*n+5)
printf("-1\n");
else
{
for(int i=1;i<=n-5;i++)
printf("2 ");
solve(5,m-n*2+10);
}
}
return ;
}
return ;
}
int main(int argc,const char *argv[])
{
freopen("divide.in","r",stdin);
freopen("divide.out","w",stdout);
int T=read();
while(T--)
{
int n=read(),m=read();
solve(n,m);
}
return 0;
}
B.color
题意
给定一棵以 $1$ 为根的树,对其染色,要求满足若干 A,B 限制。
-
A 限制:以 $pos$ 为根节点的子树内至少要有 $num$ 个节点被染色
-
B 限制:以 $pos$ 为根节点的子树外至少要有 $num$ 个节点被染色
Solution
先考虑 $B=0$ 的情况,直接贪心在树上合并,做一个类似 dp 的东西,处理出每个节点的子树中至少要染色的节点数。
然后发现答案具有单调性,大于最优答案的所有答案都可以满足条件。考虑二分,对于当前 $mid$ 去对整棵树 dfs 一次。设当前考虑到的点为 $pos$,则对于一个 B 限制,$pos$ 的子树外要染 $num$ 个节点等价于 $pos$ 的子树内至多染 $mid-num$ 个节点。这样只需要 check 每个节点是否满足上下界限制即可。
Code
#include<bits/stdc++.h>
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
struct edge
{
int to,nxt;
};
bool flag;
int n,tot,mid,ans;
bool color[100010];
int head[100010],num[100010];
int size[100010],used[100010],rst[100010];
struct edge ed[200010];
void add_edge(int fr,int to)
{
ed[++tot]=(edge){to,head[fr]};
head[fr]=tot;
return ;
}
void init(int fr,int fa)
{
size[fr]=1;
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa)
continue;
init(to,fr);
size[fr]+=size[to];
used[fr]+=used[to];
}
if(num[fr]==0)
return ;
if(size[fr]<num[fr])
{
printf("-1");
exit(0);
}
used[fr]=std::max(used[fr],num[fr]);
return ;
}
void dfs(int fr,int fa)
{
num[fr]=mid-rst[fr];
int tmp=1;
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa)
continue;
dfs(to,fr);
tmp+=num[to];
}
num[fr]=std::min(tmp,num[fr]);
if(num[fr]<used[fr])
flag=false;
return ;
}
int main(int argc,const char *argv[])
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
n=read();
for(int i=1;i<n;i++)
{
int fr=read(),to=read();
add_edge(fr,to);
add_edge(to,fr);
}
int q=read();
for(int i=1;i<=q;i++)
{
int pos=read(),tmp=read();
num[pos]=std::max(num[pos],tmp);
}
init(1,0);
q=read();
for(int i=1;i<=q;i++)
{
int pos=read(),tmp=read();
if(tmp>n-size[pos])
{
printf("-1");
return 0;
}
rst[pos]=std::max(rst[pos],tmp);
}
int left=0,right=n;
while(left<right)
{
mid=(left+right)>>1;
flag=true;
dfs(1,0);
if(flag==true)
right=mid;
else
left=mid+1;
}
printf("%d",left);
return 0;
}
C.query
题意
给定一个长度为 $n$ 的排列 $p$,$m$ 次给定 $l,r$ 求:$$ \sum^{r}_{i=l}\sum^{r}_{j=i+1}\sum^{r}_{k=j+1} p_k\times[\gcd(p_i,p_j)=p_k] $$
Solution
离线,然后乱搞。固定右端点 $r$,维护每个 $l$ 的答案。
Code
D.compete
题意
给定一个 01 串,两个人轮流取数,A 只能取 $0$,B 只能取 $1$,问两人都采取最优策略下,是否有人必胜或全部取完后平局。
Solution
一眼秒了,直接双指针模拟。设当前能取的数为 $opt$,左右端点分别为 $l,r$,则有以下情况:
-
$l,r$ 中只有一个权值为 $opt$,则必取该点;
-
$l,r$ 权值都不为 $opt$ 当前操作者败;
-
$l,r$ 权值都为 $opt$;
-
$l+1,r-1$ 中有一个权值为 $opt$,则取连续两个的那一段,此时操作者必胜;
-
$l+1,r-1$ 权值都为 $!opt$ 且 $l+2,r-2$ 权值都为 $!opt$ 则无论取哪个都必败;
-
$l+1,r-1$ 权值都为 $!opt$ 且 $l+2,r-2$ 权值中有一个为 $opt$ 则显然取往后第二个为 $opt$ 的那侧更优。
-
模拟即可。
Code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
int val[100010];
string solve()
{
int n=read();
for(int i=1;i<=n;i++)
scanf("%1d",&val[i]);
int fr=1,to=n,opt=0;
while(to>=fr)
{
if(val[fr]==opt&&val[to]==opt)
{
if(val[fr+1]==opt) fr++;
else if(val[to-1]==opt) to--;
else if(val[fr+2]==opt) fr++;
else if(val[to-2]==opt) to--;
else fr++;
}
else if(val[fr]==opt) fr++;
else if(val[to]==opt) to--;
else
return (opt==0)?"ymw\n":"sch\n";
opt^=1;
}
return "neck and neck\n";
}
int main(int argc,const char *argv[])
{
freopen("compete.in","r",stdin);
freopen("compete.out","w",stdout);
int T=read();
while(T--)
cout<<solve();
return 0;
}
result
看完题觉得 D 题很水直接 10min 写掉了,然后去写了个 A $n\le2$ 的 20pts,然后发现 C 题 $n,m\le 100$ 的部分分十分好写就写掉了。然后去想 C 题,做完 $B=0$ 的部分之后发现答案可以二分,写掉了,此时大概在 3h30min 左右。预计得分 20+100+30+100=250pts。后来一个小时一分没写。
挂分了。B 题上界没判好然后 $B=0$ 的 30pts 都挂掉了。蚌。

浙公网安备 33010602011771号