2024 暑期牛客多校训练营 10
A Surrender to My Will
签到题
B std::pair
模拟,建立二叉树即可
D Is it rated?
题目大意
有 $n$ 场$\textbf{按顺序}$的比赛,第 $i$ 场比赛有表现分 $p_i$。参加第 $i$ 场比赛后你的分数 $r$ 将变为 $r\times(1-k)+k\times p_i$。你可以选择最多 $m$ 场比赛不参加。给定初始分数 $r_0$ 和参数 $k$。问经过至少 $n-m$ 场比赛后,分数最高是多少。
题解做法
根据数据范围 $k\geq0.1$, 经过至多 200 场后之前的分数影响将在精度误差之内, 故只需要考虑最后 $\min(m+200,n)$ 场比赛即可.
场上某大佬做法(可忽略 k 范围)
const int N = 1e6 + 5;
ll a[N];
db k, p[N];
#define vt vct<db>
vt dfs (int l, int r)
{
if (l == r) rty {0, k * a[l]};
int m = l + r >> 1;
vt L = dfs (l, m);
vt R = dfs (m + 1, r);
int i = 0, j = 0;
vt ans = {0};
while (i + 1 < L.sz && j + 1 < R.sz)
{
if (L[i] * p[j + 1] + R[j + 1] > L[i + 1] * p[j] + R[j])
j++, ans.pb (L[i] * p[j] + R[j]);
else
i++, ans.pb (L[i] * p[j] + R[j]);
}
while (i + 1 < L.sz ) i++, ans.pb (L[i] * p[j] + R[j]);
while (j + 1 < R.sz ) j++, ans.pb (L[i] * p[j] + R[j]);
rty ans;
}
void solve()
{
cin >> n >> m >> k >> x;
p[0] = 1;
fo (i, 1, n) p[i] = p[i - 1] * (1 - k), cin >> a[i];
vt rp = dfs (1, n);
db ans = 0;
fo (i, n - m, n)
{
ans = max (ans, x * p[i] + rp[i]);
// print (x * p[i] + rp[i])
}
sp (12);
ANS;
rty;
}
用类似归并排序的方式来求区间内选择 $1\sim len$ 场的最大得分. O($n\log n$)
分析: 在相同场次下, 不同的选取方式大小关系与初始分数无关. 基于原始 dp $f_{i,j}$ 表示前 $i$ 场, 选了 $j$ 场参加的最大得分. 寻找性质快速合并.
下面来证明正确性:

F Collinear Exception
按顺序模拟,可以加入时暴力标记新增直线覆盖的点,经分析复杂度正确 O(能过)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fi first
#define se second
#define ls (rt<<1)
#define rs (rt<<1|1)
#define Ls (tr[rt].lc)
#define Rs (tr[rt].rc)
const int N=1e3+10;
int n,vis[N][N];
vector<pii>ans;
char s[N*N];
void add(int x1,int y1,int x2,int y2)
{
int dltx=(x1-x2),dlty=(y1-y2),gd=__gcd(abs(dltx),abs(dlty));
dltx/=gd;
dlty/=gd;
int nx=x1,ny=y1;
while(nx>=1&&nx<=n&&ny>=1&&ny<=n)
{
vis[nx][ny]=1;
nx+=dltx;
ny+=dlty;
}
nx=x1,ny=y1;
while(nx>=1&&nx<=n&&ny>=1&&ny<=n)
{
vis[nx][ny]=1;
nx-=dltx;
ny-=dlty;
}
return;
}
int main()
{
read(n);
for(int i=1,x,y;i<=n*n;i++)
{
read(x),read(y);
if(!vis[x][y])
{
s[i]='1';
vis[x][y]=1;
for(auto p:ans)
add(p.fi,p.se,x,y);
ans.push_back(make_pair(x,y));
}
else s[i]='0';
}
puts(s+1);
flushout();
return 0;
}
H All-in at the Pre-flop
诈骗题 答案为 $\dfrac{a}{a+b}$
J Doremy's Starch Trees
换根 dp,维护子树内部点是否满足要求,换根时当前根的新子树 dfs 序是新根 dfs 序的补集。用 dfs 重新编号对每个点的边排序然后二分可以 O($\log n$) 判断是否存在合法边。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int T,n,dfn[N],Time,last[N],f[N],eto[N];
vector<int>e[N],e2[N];
void dfs(int now,int fa)
{
dfn[now]=++Time;
for(int to:e[now])
if(to!=fa)dfs(to,now);
last[now]=Time;
return;
}
bool find(int x,int l,int r)
{
if(l>r)return 0;
if(e2[x].back()<l)return 0;
if(e2[x][0]>r)return 0;
int pos=0;
int L=0,rr=e2[x].size()-1,mid;
while(L<=rr)
{
mid=(L+rr)>>1;
if(e2[x][mid]>=l)pos=mid,rr=mid-1;
else L=mid+1;
}
return e2[x][pos]<=r;
}
void dfs2(int now,int fa)
{
f[now]=1;
for(int to:e[now])
if(to!=fa)
{
dfs2(to,now);
f[now]&=f[to];
eto[to]=find(now,dfn[to],last[to]);
f[now]&=eto[to];
}
return;
}
int ans=-1;
void dfs3(int now,int fa)
{
// printf("%d %d %d\n",now,fa,f[now]);
if(f[now])
{
ans=now;
return;
}
vector<int>g;
g.clear();
g.resize(e[now].size());
int l=e[now].size();
for(int i=0;i<l;i++)
if(e[now][i]!=fa)g[i]=f[e[now][i]]&eto[e[now][i]];
else g[i]=1;
for(int i=l-2;i>=0;i--)
g[i]&=g[i+1];
int fg=1;
for(int i=0;i<l;i++)
{
int to=e[now][i];
if(to!=fa)
{
if(fg&&(i<l-1?g[i+1]:1)&&(find(to,1,dfn[to]-1)||find(to,last[to]+1,n)))
dfs3(to,now);
fg&=eto[to]&f[to];
}
}
}
int main()
{
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++)e2[i].clear(),e[i].clear();
for(int i=2,p;i<=n;i++)
{
cin>>p;
e2[p].push_back(i);
e2[i].push_back(p);
}
for(int i=2,p;i<=n;i++)
{
cin>>p;
e[p].push_back(i);
e[i].push_back(p);
}
Time=0;
dfs(1,0);
for(int i=1;i<=n;i++)
{
for(int j=0;j<e2[i].size();j++)e2[i][j]=dfn[e2[i][j]];
sort(e2[i].begin(),e2[i].end());
}
dfs2(1,0);
ans=-1;
dfs3(1,0);
cout<<ans<<'\n';
}
flushout();
return 0;
}
/*
1
4
1 2 3
1 1 1
*/
K Doremy's IQ 2
显然是先往小走再往大走(或者反过来),枚举最小走到哪,最大的端点单调不增可以用双指针维护。O(n)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,n,a[N],ans;
int work()
{
int l=1,r=n,res=0;
for(int l=n;l;l--)
if(l>(-a[l])&&a[l]<=0)
{
while(a[r]>0&&n-r<a[r]-a[l])r--;
res=max(res,r-l+1);
}
return res;
}
int main()
{
read(T);
while(T--)
{
read(n);
for(int i=1;i<=n;i++)read(a[i]);
ans=work();
for(int i=1;i<=n;i++)a[i]=-a[i];
reverse(a+1,a+n+1);
ans=max(ans,work());
write(ans-1);
}
flushout();
return 0;
}
L Tada!
考虑枚举密码,将状态 $A$ 变为 $B$ 所需的步数等于 $A-B$ (每一位在模 $10$ 意义下分别做减法) 通过区间加减 $1$,变为 $00000$ 的步数。 不难发现操作可逆,所以 $A-B$ 变为 $00000$ 的最少步数等于 $00000$ 变为 $A-B$ 的步数,所以从 $00000$ 出发 $\text{bfs}$ 求出每个 $A-B$ 的最少步数即可,记作 $f\_{A-B}$ 当 $n>1,t_i>1$ 时,只要 $f_{A-B}\le t_i$ 一定可以成功,与奇偶性无关。 当 $n=1$ 或 $t_i=1$ 时,则需考虑奇偶性。 O($m10^n$)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,n,m,cnt[N],d[6],D[6],num[6],f[N];
void init()
{
memset(f,-1,sizeof(f));
f[0]=0;
queue<int>q;
q.push(0);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int j=5,nw=now;j;j--)
num[j]=nw%10,nw/=10;
for(int i=1;i<=5;i++)
for(int j=i,v;j<=5;j++)
{
for(int k=i;k<=j;k++)
num[k]++,num[k]%=10;
v=0;
for(int k=1;k<=5;k++)
v=v*10+num[k];
if(f[v]==-1)
{
f[v]=f[now]+1;
q.push(v);
}
for(int k=i;k<=j;k++)
num[k]+=18,num[k]%=10;
v=0;
for(int k=1;k<=5;k++)
v=v*10+num[k];
if(f[v]==-1)
{
f[v]=f[now]+1;
q.push(v);
}
for(int k=i;k<=j;k++)
num[k]++,num[k]%=10;
}
}
return;
}
int main()
{
init();
read(T);
while(T--)
{
read(n),read(m);
int mx=1;
for(int i=1;i<=n;i++)mx*=10;
for(int i=0;i<mx;i++)cnt[i]=0;
for(int i=1,s,t;i<=m;i++)
{
read(s),read(t);
int ns=s;
for(int j=n;j;j--)
num[j]=ns%10,ns/=10;
for(int j=1;j<=n;j++)d[j]=num[j];
for(int as=0,now,v;as<mx;as++)
{
now=as;
for(int j=n;j;j--)
num[j]=now%10,now/=10;
for(int j=1;j<=n;j++)
D[j]=num[j]-d[j],D[j]=(D[j]%10+10)%10;
v=0;
for(int j=1;j<=n;j++)v=v*10+D[j];
if(f[v]==0&&t==1)continue;
if(n==1&&(abs(t-f[v])&1))continue;
if(f[v]<=t)cnt[as]++;
}
}
int ans=0,pos=0;
for(int i=0;i<mx;i++)
if(cnt[i]==m)ans++,pos=i;
if(ans>1)puts("MANY");
else if(ans==1)
{
for(int j=1;j<=n;j++)num[j]=pos%10,pos/=10;
for(int j=n;j;j--)
putchar(num[j]+'0');
putchar('\n');
}
else puts("IMPOSSIBLE");
}
flushout();
return 0;
}
/*
1
3 3
003 1
003 3
025 1
*/