湖南多笑 9
组队出道
zzomega 左ls
K2SO4 张ls
没有ID 刘ls
4h过6题
题解
B
张ls开的,我跟着一起看看
首先有没有圆没有区别
其次考虑维护“纯粹的”相交,即没有包含关系的相交。
这样的话直接维护相交就会产生不要求的“包含”关系。
所以考虑维护不交。
按照右端点排序,在\(r_i<r_j\)的情况下,\(i,j\)不交的两种状况
\(r_i<l_j\) 或者\(l_i>l_j\)
两个树状数组维护一下就行
C
当时我考虑这是一个数论题
exgcd完了就没事了,然后通解也能构造
不过左ls发现可能会出像6 8 10 这种反例
然后左ls发现数据范围超小,可以直接背包
遂直接背包。
后面的输出数字记录状态转移即可
D
其实答案的上界是 \(O(\log^2 t)\),证明不难。
然后我们给了一个类似倍增的做法。
在走到"下一次冲刺就会冲过头"的时候,选择一种策略
要么冲过头返回,要么刹车走剩下的一段。
从中选择最优策略,其余情况直接无脑冲刺显然更优。
这个时候直接搜会超时,加个记忆化就过了
左ls直接搜寄了,然后我给加个玄学记忆化就过了
正经的贪心过程事实上会把这题提升到几乎不可做的难度
E
我干的
假设第\(i\)天合法的评分集合为\(A_i\)
那么\(\forall j>i,A_i\subseteq A_j\)
所以实际上对于第\(i+1\)天来说,前\(i\)天的选择肯定会占掉\(i\)个位置
而且事实上显然也只会占这么多。
那只需要考虑第\(i+1\)天\(A_{i+1}\)的大小。
即所有满足\(a_j\geq\frac{k}{i+1},\)的数量
那么就是\(n-\lceil\frac{k}{i+1}\rceil+1\)
减去\(i\)个已经用的即可。
状态转移方程:
F
这个也是我干的
注意到如果能直接停电车的话肯定直接骑电车零代价到
否则就得考虑走着走,
注意到空手去拉一个点的物资回电车一定更优,
所以也就是说本质上是求
其中\(dis_i\)表示\(i\)到任意一个能停电车的点的最短距离
这个问题就是经典的建虚点跑最短路问题
注意给距离取模就没了
M
大概就是按照点对距离取点更新距离
然后每确定一个点的半径就去用距离减半径更新其他点。
这里我开始考虑的是把初始的点对连带着更新的点对一起扔进堆里
每次取最小的点对出来更新
但是其实更新就直接更新就行了,没有其他的乱七八糟的。
L
我们准时下班不搞别的
所以这题四点下班的时候写了一半然后扔了跑路
不过当然思路是已经成熟的
首先很显然先把小的扔到两边然后才会动大的
由于邻项交换不改变相对顺序,所以把小的搞定之后看大的那些相对顺序其实是不会变的。
也就是等价于直接把小的删掉。
然后就是考虑次数。
无非就是往左或者往右扔,取最小的那边肯定更优。
所以只需要维护当前位置上现在是第几个数。
考虑每个位置初始都是\(1\),每删掉一个位置的数,就把这个位置变成\(0\)
这个数组的前缀和,其实就是初始在\(i\)的数删数后的位置。
考虑到有相同值的时候排序不好排,可以把相同的几个元素同时做
当然也可以从大到小做,往回插
A
被诈骗了T_T
按照常理来说这题应该是树形DP
但是这个题如果能带修带查的话,
那么单次知道答案的时间成本至少不是重新做一遍树形DP
那么换种思维考虑。
如果一个节点\(i\)和它的子节点\(j\)不同色,
那么这个节点\(j\)一定会翻
接下来整棵树和根节点\(rt\)同色,再看\(a_{rt}\)决定翻不翻
所以实质上只需要考虑有多少条边\((u,v)\)满足\(a_u\not=a_v\)
这很简单,维护子节点的个数\(d\)和子节点中和自己不同色的个数\(c\)
那么翻转节点\(i\)本质上是\(c_i\leftarrow d_i-c_i,c_{fa_i}\)视情况更新
数据结构可以单次\(\log\),不过当然直接维护答案就行,单次\(O(1)\)
1A
#include <bits/stdc++.h>
using namespace std;
const int o=222222;
int n,q,T;
struct Graph{
struct edge{
int t,n;
}p[o];
int d[o],c[o],x,h[o],cnt,a[o],f[o],rt,ans;
void add(int s,int t){
cnt++;
p[cnt].t=t;
p[cnt].n=h[s];
h[s]=cnt;
}
void dfs(int x,int fa){
for(int i=h[x];i;i=p[i].n){
int y=p[i].t;
if(y==fa)continue;
d[x]+=1;
if(a[y]!=a[x])c[x]+=1;
f[y]=x;
dfs(y,x);
}
ans+=c[x];
}
void flip(int x){
if(x!=1){
if(a[f[x]]==a[x])c[f[x]]++,ans++;
else c[f[x]]--,ans--;
}
if(x==rt){
if(a[x])ans--;
else ans++;
}
ans-=c[x];
c[x]=d[x]-c[x];
ans+=c[x];
a[x]^=1;
}
void changeroot(int x){
ans-=a[rt];
rt=x;
ans+=a[rt];
}
int ques(){return ans;}
void clear(){
for(int i=1;i<=cnt;i++)p[i]=p[0];
for(int i=1;i<=n;i++){
h[i]=0,c[i]=0,a[i]=0,f[i]=0,d[i]=0;
}
rt=0,ans=0,cnt=0;
}
}G;
int read(){
int i=1,j=0;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-')i=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
j=j*10+ch-48;
ch=getchar();
}
return i*j;
}
void in(){
n=read(),q=read();
for(int i=1;i<=n;i++)G.a[i]=read();
for(int i=1,x,y;i<n;i++){
x=read(),y=read();
G.add(x,y);
G.add(y,x);
}
}
void pre(){
G.ans=G.a[1];
G.rt=1;
G.dfs(1,0);
printf("%d\n",G.ques());
}
void work(){
int op,x;
for(int i=1;i<=q;i++){
op=read(),x=read();
if(op==1)G.flip(x);
if(op==2)G.changeroot(x);
printf("%d\n",G.ques());
}
}
int main(){
T=read();
while(T--){
in(),pre(),work(),G.clear();
}
return 0;
}
I
这个题也是一开始就开的,一直在想申必哈希
其实好像不用哈希,直接做就行
题解给了几个简单的性质
性质一:合法的子串以\(1\)开头
这个没啥好解释的,如果你开头连\(1\)都不是,那你肯定是没有办法删成一个开头是\(1\)的序列
性质二:合法的子串末尾加个\(1\)还是合法的
这个说白了就是直接上操作在末尾插\(1\)而已,很显然
性质三:如果\(a_{i+1}=a_i+1\),那么\(a_i\)和\(a_{i+1}\)是同一次操作插入的
因为一次不可能直接从\(2\)开始插入,所以不可能说先插\(a_{i+1}\)再插\(a_i\)
所以只能是先插\(a_i\)再插\(a_{i+1}\)
这样的话如果\(a_i>1\)的话那这两个只能是跟别的一起插进来
否则\(a_i=1,a_{i+1}=2,\)仍然可以看作是一起插进来的,证毕
性质四:如果\([l,r]\)中间有极大合法子串\([p,q]\),则\([l,r]\)合法等价于删去\([p,q]\)后的序列合法。
这个的话就是删掉后的序列合法,那么按照构造\([p,q]\)的方法重新加回去还是合法。
删掉的部分不合法,那么\([l,r]\)肯定也不会合法。
根据上述性质,仍然考虑经典的以\(i\)结尾的合法串数量\(g_i\)。
综合性质一和性质二,以\(1\)结尾的\(i\),合法串数量包括自己和前面所有合法的串\(g_{i-1}\)
即$$g_i=g_{i-1}+1,a_i=1$$
否则就要找一个同期加入的,并且中间一段合法的\(j\),\(g_i=g_j\)
这个\(j\)只需要考虑在\(i\)前最近的一个
考虑具体的做法,这个过程类似于一个括号匹配,不过还是不太显然。
维护一个二元组,第一个是下标\(pos\),第二个是\(val=a_{pos}\)
记这个二元组所组成的栈顶为\(top\).
如果\(val_{top}=a_i-1\),那么说明\(pos_{top}\)就是\(i\)所对应的满足条件的\(j\),然后再更新一下\(top\)就完了。
否则就不是,将其出栈。
如果是\(a_i=1\)的话直接新建一个节点入栈即可。
一发切
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int o=2222222;
int g[o],a[o],n,ans;
struct node{
int pos,val;
}c;
stack<node>q;
void in(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
}
void work(){
for(int i=1;i<=n;i++){
if(a[i]==1){
if(q.empty())g[i]=1;
else g[i]=g[q.top().pos]+1;
c.pos=i,c.val=1;
q.push(c);
continue;
}
while(!q.empty()){
c=q.top(),q.pop();
if(c.val==a[i]-1){
g[i]=g[c.pos];
c.pos=i,c.val=a[i];
q.push(c);
break;
}
}
}
for(int i=1;i<=n;i++)ans+=g[i];
}
void out(){cout<<ans<<"\n";}
#undef int
int main(){
in(),work(),out();
return 0;
}
H
主要是考虑两个问题,
第一个是任意长的整数拆分里面怎么去算所有的数对
第二个是去掉任意一个\(c_i\)怎么做
这个显然从序列下手就绝对平等的卡死每一个人。
注意到值域很小,能够从值域下手枚举每个数对。
那么接下来只需要考虑两个数\((i,j)\)出现的次数就好了
考虑数对\((x,y),x\not=y\) 的情况,
假设一个序列里有\(a\)个\(x\)和\(b\)个\(b\),那么\((x,y)\)做的贡献次数是\(a\cdot b\)
恰好枚举\(i\in [1,a],j\in [1,b]\)的话就可以把这个序列的所有贡献算完并且不会重复。
所以我们在算\((x,y)\)的贡献总次数,其实就是\(\sum\limits_{ix+jy\leq n}f_{n-ix-jy}\)
这里枚举\(i,j,x,y\)即可通过,时间复杂度\(O(n^2\log^2 n)\)
考虑更进一步的优化就是把\(\sum\limits_{ix+jy\leq n}f_{n-ix-jy}\)对每个\(y\)和\(s=n-ix\)进行预处理
这一部分的处理直接暴力枚举\(y\)和\(s\),直接对所有合法的\(j\)进行累加。
之后的话就可以只枚举\(s\)
两部分时间复杂度都是\(O(n^2\log n)\)
最后考虑 \(x=y\) 的情况
这一部分只需要枚举\(x\)和\(i\),
仍然考虑一个序列贡献次数对\(i\)拆分。
如果一个\(x\)出现了\(a\)次的话,总贡献次数是\(\frac{a(a-1)}{2}\)
这样的话做拆分,每个\(i\)算\((i-1)\),
把这一部分连带剩余的方案数\(f_{n-ix}\)一起乘起来就是总贡献数
所以每个\(x\)的总贡献是\(f_{n-ix}\cdot x(i-1)\)
AI写的码一遍过了,我的码WA了十几发
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int o=2222,mod=1e9+7;
int n,m,c[o],f[o],g[o][o],ans;
//g[j][i]:y对应j,n-s对应i时需要的f之和
void in(){
cin>>n>>m;
for(int i=1,x;i<=m;i++){
cin>>x;
c[x]=1;
}
}
void pre(){
f[0]=1;
for(int i=1;i<=n;i++){
if(c[i])continue;
for(int j=i;j<=n;j++)f[j]=(f[j]+f[j-i])%mod;
}
/*for(int i=1;i<=n;i++)cout<<f[i]<<" ";
puts("");*/
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
//if(c[j])continue;
for(int k=1;k<=i/j;k++)g[j][i]=(g[j][i]+f[i-k*j])%mod;
}
}
/*for(int j=1;j<=n;j++){
for(int i=1;i<=n;i++)cout<<g[j][i]<<" ";
cout<<"\n";
}*/
}
void work(){
for(int x=1;x<=n;x++){
for(int y=x+1;y<=n;y++){
if(c[x]||c[y])continue;
int now=__gcd(x,y);
for(int i=1;i*x<=n;i++)ans=(ans+now*g[y][n-i*x]%mod)%mod;
}
}
for(int x=1;x<=n;x++){
if(c[x])continue;
for(int i=1;i<=n/x;i++)ans=(ans+x*(i-1)%mod*f[n-i*x]%mod)%mod;
//for(int j=1;j<=i;j++)ans=(ans+x*(j-1)%mod*f[n-j*x]%mod)%mod;
}
}
void out(){cout<<ans<<"\n";}
#undef int
int main(){
in();pre();work();out();
return 0;
}

浙公网安备 33010602011771号