A 托米的字符串
题意:
给一个字符串,求在其中随机取字串,字串中元音所占的比例的期望
解题思路:
要是用每个元音各自对最终答案的贡献算的话比较麻烦,可以考虑分子串长度算所有元音的贡献。
假设最后再除以总子串数目n*(n+1)/2,我们来计算最终答案的分子。
当子串长度为1时,每个元音对答案贡献为1/1(分母代表子串长度),统计元音个数即可。当子串长度为2时,位于字符串首和尾的元音只能被取到一次,贡献为1/2,其余位置的元音可以被取到两次,贡献为2/2。字串长度为3时,首尾元音贡献1/3,第二位和倒数第二位为2/3, 其余位置为3/3。依次类推,用元音数目的前缀和计数一下就行了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1001000
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
string s;
double qzh[maxn], ans;
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>s;
int n=s.size();
s=" "+s;
for (int i=1; i<=n; i++)
{
qzh[i]=qzh[i-1];
char c=s[i];
if (c!='a' && c!='e' && c!='i' && c!='o' && c!='u' && c!='y') continue;
qzh[i]=qzh[i]+1;
}
double temp=0;
for (int i=1; i<=n; i++)
{
temp+=qzh[n-i+1]-qzh[i-1];
ans+=temp/i;
}
cout<<fixed<<setprecision(10)<<2*ans/n/(n+1)<<endl;
}
C 纳新一百的石子游戏
题意:
n个石子堆,第i个石子堆有ai个石子。分别对前1堆,前2堆…前n堆玩一次游戏,每次求出先手者保证必胜情况下第一步有几种取法。
解题思路:
由SG函数可知,对于当前进行游戏的一方,若所有石子堆剩余石子个数异或和不为零则可保证有必胜走法。因此先手者要保证走完第一步后能必胜,就必须保证留给对手的局面所有石子堆异或和为0。
假设现在在对前k堆进行游戏,前k堆石子的异或和为ksum,将第i堆石子(原来有ai颗)取到剩下bi颗,则总石子异或和为ksum^ai^bi,要使其为0,就等于要让ksum^ai == bi,也就是要求ksum^ai <= ai。
于是问题就转化为寻找符合上面那个不等式的石子堆的数目。自高位向低位按位考虑ksum,如果ksum这位为0,那么无论某个石堆石子颗数这位为0还是1,异或完都仍是0或1,无法区分异或后值谁更大。但若ksum这位为1,石子堆若这位为1,异或和这位为0,则异或和一定小于原石子颗数,也就是这堆石子对答案贡献为1;石子堆若这位为0,则异或和这位为1,异或和一定大于原石子颗数。
所以只要从高位到低位找到ksum的第一个非零的位置,加上在这一位为1的石子堆数就行了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf 223372036854775807
#define maxn 201000
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const double eps=1e-9;
const double PI=acos(-1.0);
ll n, s, fi, cot[110];
int main()
{
cin>>n;
for (ll i=1, x; i<=n; i++)
{
cin>>x;
s^=x;
for (fi=60; fi>=0; fi--)
if (s&(1ll<<fi)) break;
for (ll j=60; j>=0; j--)
if (x&(1ll<<j)) cot[j]++;
if (s==0)
cout<<0<<"\n";
else cout<<cot[fi]<<"\n";
}
return 0;
}
E 阔力梯的树
题意:
给一颗树,节点标号为1~n,定义一个点的”结实程度“为该节点的子树中所有点编号排序后相邻值的差的平方和。求每个节点的“结实程度”。
解题思路:
启发式合并板题,用set维护子树序列,每次仅对当前点与非重儿子更新set,更新set时插入一个值判断一下相邻有没有值并更新答案。如果当前点是其父亲的重儿子,则计算完后保留set,否则清空set。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf 223372036854775807
#define maxn 201000
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const double eps=1e-9;
const double PI=acos(-1.0);
vector<ll> edge[maxn];
ll n, siz[maxn], son[maxn], ans[maxn];
void dfs(ll now, ll fa)
{
siz[now]=1;
for (auto to: edge[now])
{
if (to==fa) continue;
dfs(to, now);
siz[now]+=siz[to];
if (siz[to]>siz[son[now]]) son[now]=to;
}
}
ll res;
set<ll> st;
void dsu2(ll now, ll fa, ll son)
{
for (auto to: edge[now])
{
if (to==fa || to==son) continue;
dsu2(to, now, son);
}
set<ll>::iterator iterl, iterr;
ll flagr=0, flagl=0;
iterr=st.upper_bound(now);
if (iterr!=st.end()) flagr=1;
if (iterr!=st.begin()) iterl=iterr, iterl--, flagl=1;
if (flagr) res+=(*iterr-now)*(*iterr-now);
if (flagl) res+=(*iterl-now)*(*iterl-now);
if (flagl && flagr) res-=(*iterr-*iterl)*(*iterr-*iterl);
st.insert(now);
}
void dsu(ll now, ll fa, ll opt)
{
if (edge[now].size()==0)
return ;
for (auto to: edge[now])
{
if (to==fa || to==son[now]) continue;
dsu(to, now, 0);
}
if (son[now]) dsu(son[now], now, 1);
dsu2(now, fa, son[now]);
ans[now]=res;
if (!opt) res=0, st.clear();
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for (ll i=2, u; i<=n; i++)
{
cin>>u;
edge[u].push_back(i);
edge[i].push_back(u);
}
dfs(1, 0);
dsu(1, 0, 1);
for (int i=1; i<=n; i++)
cout<<ans[i]<<"\n";
return 0;
}
F 采蘑菇的克拉莉丝
题意:
给一颗树,有边权。初始起点为1号点,共有q个操作,一种是修改起点,一种是给v号节点加上x的权值。定义答案为每个点的点权乘以这个点到起点的路径上离起点最近的边的边权。要求每次操作后输出答案。
解题思路:
思路神奇。
很容易想到的暴力是用树链剖分维护,对当前起点的每个儿子求一下点权和然后乘以到儿子的边的边权(起点子树外的点用 总点权和 减 起点子树点权和 再乘以 起点到其父亲的边的边权 )。但这样算在菊花图的情况下肯定会T的一塌糊涂。
神奇的操作是,我们在计算起点儿子的贡献时可以只对起点的重儿子进行上述操作,而其它轻儿子的贡献则可以在修改点权的时候事先计算好。每次修改点权时向上跳,对所有轻边的父亲节点更新答案(修改的点权*轻边的边权),由于树链剖分的性质,每个点到根节点中不会有超过logn个轻边。
修改操作复杂度O(log(n)*log(n)),查询操作复杂度O(log(n))。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1001000
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
ll n, q;
int son[maxn], siz[maxn], fa[maxn], deep[maxn];
int top[maxn], id[maxn], va[maxn];
int bu[maxn], bv[maxn], bw[maxn];
ll cnt, ans[maxn];
vector<int> edge[maxn];
ll tr[maxn+5];
void add(ll x, ll v)
{
for (ll i=x; i<maxn; i+=(i&-i))
tr[i]+=v;
}
ll query(ll x)
{
ll ans=0;
for (ll i=x; i; i-=(i&-i))
ans+=tr[i];
return ans;
}
void dfs1(ll now, ll f, ll dep)//预处理出深度,父亲数组,重儿子数组,子树大小数组
{
fa[now]=f; deep[now]=dep; siz[now]=1;
ll maxson=-1;
for (auto &to:edge[now])
{
if (to==f) continue;
dfs1(to,now,dep+1);
siz[now]+=siz[to];
if (siz[to]>maxson) maxson=siz[to],son[now]=to;
}
}
void dfs2(ll now, ll topf)//预处理出线段树映射数组,映射点值数组,重链顶部数组
{
id[now]=++cnt; top[now]=topf;
if (!son[now]) return ;
dfs2(son[now],topf);
for (auto &to:edge[now])
{
if (to==fa[now] || to==son[now]) continue;
dfs2(to,to);
}
}
ll query_st(ll x)//查询以该点为根的子树(包括该点)节点值的和
{
return query(id[x]+siz[x]-1)-query(id[x]-1);
}
void modi(ll x, ll v)
{
add(id[x], v);
while (top[x]!=1)
{
ans[fa[top[x]]]+=v*va[top[x]];
x=fa[top[x]];
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for (ll i=1, u, v, w; i<n; i++)
{
cin>>u>>v>>w;
edge[u].push_back(v);
edge[v].push_back(u);
bu[i]=u, bv[i]=v, bw[i]=w;
}
dfs1(1, 0, 1);
for (int i=1; i<n; i++)
{
if (deep[bu[i]]<deep[bv[i]])
va[bv[i]]=bw[i];
else va[bu[i]]=bw[i];
}
dfs2(1, 1);
cin>>q;
ll befv=1;
for (ll i=1, op, v=1, x; i<=q; i++)
{
cin>>op>>v;
if (op==2) befv=v;
if (op==1)
{
cin>>x;
modi(v, x);
}
ll res=ans[befv];
res+=(query_st(1)-query_st(befv))*va[befv];
if (son[befv])
res+=query_st(son[befv])*va[son[befv]];
cout<<res<<"\n";
}
return 0;
}
H 叁佰爱抠的序列
题意:
给一个n,要求构造出一个序列满足下列条件:自己选取一个尽量大的整数m,然后使所有序列元素值都处于[1,m]之间,并且对于[1,m]中任意一对不相等的数,在序列中都存在相邻的关系。
解题思路:
首先不难把题目转化为构造一个m个点的图,有n条边,图中任意两点都有至少一条边,且存在欧拉路径。
对于寻找最大的m,可以采用二分的做法。如果点数是奇数,那么完全图就满足存在欧拉路径(回路)的要求,对n的要求是n>=m*(m+1)/2 + 1 (要再加个1是因为相比于边序列还要输出起点所以多要多1),如果点数是偶数,则在完全图的基础上要再增加m/2-1条边,保证仅有两个点的度数为奇数,就可以确保存在欧拉路径,对n的要求是n>=m*(m+1)/2+m/2。
找到m后构建欧拉路径就行了。记得偶数点的情况下要给除起点和终点外的点都连上一条边,确保其度数为偶数。
我的构建思路是设1和n为起点和终点,首先把1,2,3…n的路径取出来,如果m为偶数则在2到3,4到5…n-2到n-1间加一条边,如果是奇数点情况就先把n到1的路径取出来(确保起点终点度数为奇数),然后在对2到n-1号点分别找以该点为起点和终点的环。最后输出1到n,输出到某个点后输出该点的环。
ps:强烈吐槽这题居然不能多输出行末空格,牛客还不会提示pe,自闭了半天才发现
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 3010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);
ll m;
vector<int> add[maxn], loop[2001000];
int n, edge[maxn][maxn], nowedge[maxn];
ll check(ll x)
{
if (x&1) return x*(x-1)/2+1;
return x*x/2;
}
void dfs(int st, int loid, int now)
{
if (now==st && loop[loid].size()!=0) return ;
while (!edge[now][nowedge[now]])
nowedge[now]++;
edge[now][nowedge[now]]--, edge[nowedge[now]][now]--;
loop[loid].push_back(nowedge[now]);
dfs(st, loid, nowedge[now]);
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>m;
ll l=1, r=2e9+10, mid;
while (l<r)
{
mid=(l+r)/2;
if (check(mid)>=m) r=mid;
else l=mid+1;
}
if (check(l)>m) l--;
cout<<l<<"\n";
if (m>2000000)
return 0;
n=l;
for (int i=1; i<=n; i++)
{
nowedge[i]=1;
for (int j=i+2; j<=n; j++)
{
edge[i][j]++;
edge[j][i]++;
}
}
if (n%2==0)
{
for (int i=2; i<n-1; i+=2)
{
edge[i][i+1]++;
edge[i+1][i]++;
}
}
if (n%2!=0) edge[1][n]--, edge[n][1]--;
int cnt=0, tot=0;
for (int i=2; i<n; i++)
{
while (true)
{
while (nowedge[i]<=n && !edge[i][nowedge[i]])
nowedge[i]++;
if (nowedge[i]>n) break;
dfs(i, ++cnt, i);
add[i].push_back(cnt);
}
}
for (int i=1; i<=n; i++)
{
cout<<i; tot++;
if (tot!=m) cout<<" ";
for (auto j: add[i])
{
for (auto k: loop[j])
{
cout<<k; tot++;
if (tot!=m) cout<<" ";
}
}
}
while (tot<m)
{
cout<<1;
tot++;
if (tot!=m) cout<<" ";
}
return 0;
}