7-1 2A. 托米的字符串
对每个元音计算它对所有包含它的区间的贡献和,累加起来除以所有可能的区间数目就是答案。
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e6 + 50;
char s[maxn];
bool is_o(char ch){
if(ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u' || ch == 'y') return 1;
else return 0;
}
double S[maxn], T[maxn];
int main()
{
scanf("%s", s);
ll n = strlen(s);
T[0] = S[0] = 0;
for(int i = 1; i <= n; ++i) S[i] = S[i-1] + (double)1.0/(double)i, T[i] = T[i-1] + S[i];
double ans = 0;
for(int i = 0; i < n; ++i){
if(is_o(s[i])) {
ans += (T[n] - T[n-i-1] - T[i]);
}
}
double fm = n;
fm = (fm*(fm+1))/2;
ans = ans/fm;
printf("%.9f\n", ans);
}
7-3 2C. 纳新一百的石子游戏
这题和队友讨论了一个憨憨做法(带删字典树)。
真实的题解是这样的:
设当前异或和为x,则要找到的就是有多少堆石头y满足y>x xor y,这样就能将y变为x xor y来使得异或和为0。考虑异或和的最高的为1的二进制位,所有这一位是1的y显然都满足条件,这一位是0的都不满足条件。(来自jls课件)
我们的代码是这样的:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
const int lim = 62;
long long a[maxn], s[maxn];
long long f[lim];
int lson[maxn*lim], rson[maxn*lim], c[maxn*lim];
int lazy[maxn*lim];
int root;
int n, sz;
void init(){
f[0]=1;
for (int i=1;i<lim;++i){
f[i]=f[i-1]<<1;
}
return ;
}
int newnode(){
sz++; lson[sz]=rson[sz]=c[sz]=0;
return sz;
}
void add(long long x){
int p=root;
c[p]++;
for (int i=lim-1;i>=0;--i){
if (x&f[i]){
if (lson[p]==0){
lson[p]=newnode();
}
p=lson[p];
}else{
if (rson[p]==0){
rson[p]=newnode();
}
p=rson[p];
}
c[p]++;
}
return ;
}
void read_data(){
scanf("%d",&n);
a[0]=s[0]=0;
lson[0]=rson[0]=c[0]=0;
root=0; sz=0;
for (int i=1;i<=n;++i){
scanf("%lld",&a[i]); s[i]=s[i-1]^a[i];
//cout<<s[i]<<"\n";
add(s[i]);
}
return ;
}
void update(int pos){
if (lson[pos]!=0&&c[lson[pos]]!=0){
lazy[lson[pos]]+=lazy[pos];
}
if (rson[pos]!=0&&c[rson[pos]]!=0){
lazy[rson[pos]]+=lazy[pos];
}
lazy[pos]=0;
return ;
}
void dfs(int p, long long x, int dep){
if (lazy[p]){
update(p);
}
if (x&f[dep]){
if (lson[p]!=0){ //1 1 加答案
lazy[lson[p]]++;
}
if (rson[p]!=0){ //1 0 dfs
if (c[rson[p]]!=0)
dfs(rson[p],x,dep-1);
}
}else{
if (rson[p]!=0){ //0 0 dfs
if (c[rson[p]]!=0)
dfs(rson[p],x,dep-1);
}
//0 1 小于,不更新
}
return ;
}
int del(long long x){
long long sum=0;
int p=0;
for (int i=lim-1;i>=0;--i){
if (lazy[p]){
update(p);
}
c[p]--;
if (x&f[i]){
p=lson[p];
}else{
p=rson[p];
}
}
c[p]--;
//cout<<lazy[p]<<"\n";
return lazy[p];
}
void sol(){
for (int i=1;i<=n;++i){
dfs(root,a[i],lim-1);
printf("%d\n",del(s[i]));
}
return ;
}
int main(){
init();
read_data();
sol();
return 0;
}
7-5 2E. 阔力梯的树
考虑DSU ON TREE,先记录重儿子。用set维护一个当前的序列,这样每次加入一个新的值很容易更新答案。然后每次统计完轻儿子的答案之后把set清空,再去统计重儿子的答案,不清空的情况下再把轻儿子的所有结点加入进来作为对当前结点子树的答案的统计。
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define pb push_back
using namespace std;
const int maxn = 1e5 + 50;
vector<int> g[maxn];
int n;
int sz[maxn], son[maxn];
void dfs1(int u){
//cout<<"u1:"<<u<<endl;
sz[u] = 1;
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i];
dfs1(v);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}return;
}
ll ans[maxn];
set<int> s;
ll cur = 0;
void add(int id){
//cout<<"id:"<<id<<endl;
if(!s.size()){
s.insert(id);
return;
}
s.insert(id);
set<int>::iterator it = s.lower_bound(id);
ll nxt, pre;
if(it == s.begin()){
nxt = *(++it);
cur += (ll)(nxt-id)*(ll)(nxt-id);
return;
}else if(it == --(s.end()) ){
pre = *(--it);
cur += (ll)(id - pre)*(ll)(id - pre);
return;
}else{
it--;
pre = *it;
it++;it++;
nxt = *it;
cur -= (nxt-pre)*(nxt-pre);
cur += (ll)(id-pre)*(ll)(id-pre);
cur += (ll)(nxt-id)*(ll)(nxt-id);
}return;
}
void dfs3(int u){
//cout<<"u:"<<u<<endl;
add(u);
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i];
dfs3(v);
}return;
}
void dfs2(int u){
//cout<<"u2:"<<u<<endl;
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i];
if(v == son[u]) continue;
dfs2(v);
s.clear();
//cout<<"---------clear!"<<endl;
cur = 0;
}
if(!son[u]) {
add(u); return;
}
dfs2(son[u]);
add(u);
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i];
if(v == son[u]) continue;
dfs3(v);
}
ans[u] = cur;
}
int main()
{
scanf("%d", &n);
for(int i = 2; i <= n; ++i){
int f; scanf("%d", &f);
g[f].pb(i);
}
dfs1(1);
dfs2(1);
for(int i = 1;i <= n; ++i){
printf("%lld\n", ans[i]);
}
}
7-6 2F. 采蘑菇的克拉莉丝
这题有一个比较显然的暴力:用数据结构动态维护每个结点子树中蘑菇的数量,然后查询的时候暴力枚举要查询的结点的儿子来更新答案。但是这种做法会被菊花图卡。
考虑用重链剖分来维护结点子树蘑菇数量,每次在结点加x个蘑菇的时候就把根到这个蘑菇的路径所有结点权值+x。每次统计的时候我们改变方法,只统计它的重儿子贡献的答案和父亲边对应的答案,那么还差轻儿子的答案没有统计。
我们把对轻儿子的答案的统计放到修改操作中去:每次修改会往上跳,每次跳其实就是跳一个轻边,那么这条轻边的权值*当前增加的值,就是对轻边父亲边的答案的增加值。这样就可以得到所有轻边的贡献了。
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define P pair<int,ll>
using namespace std;
const int maxn = 1e6 + 50;
ll lz[maxn<<2];
int dfn[maxn], idx = 0, sz[maxn], son[maxn], id[maxn], fa[maxn], top[maxn];
ll a[maxn];
ll ans[maxn];
int n, q;
vector<P> g[maxn];
void dfs1(int u, int f){
sz[u] = 1;
fa[u] = f;
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i].first;
if(v == f) continue;
ll w = g[u][i].second;
dfs1(v, u);
a[v] = w;
sz[u] += sz[v];
if(sz[son[u]] < sz[v]) son[u] = v;
}return;
}
void dfs2(int u){
dfn[++idx] = u; id[u] = idx;
if(son[u]) top[son[u]] = top[u], dfs2(son[u]);
for(int i = 0; i < g[u].size(); ++i){
int v = g[u][i].first;
if(v == fa[u] || v == son[u]) continue;
top[v] = v;
dfs2(v);
}
}
void down(int rt){
if(lz[rt]){
lz[rt<<1] += lz[rt];
lz[rt<<1|1] += lz[rt];
lz[rt] = 0;return;
}return;
}
void update(int rt, int l, int r, int L, int R, ll x){
if(L <= l && r <= R){
lz[rt]+=x;return;
}
down(rt);
if(L <= mid) update(lson, L, R, x);
if(R > mid) update(rson, L, R, x);
return;
}
ll qry(int rt, int l, int r, int pos){
if(l == r) return lz[rt];
down(rt);
if(pos <= mid) return qry(lson, pos);
else return qry(rson, pos);
}
void init(){
scanf("%d", &n);
for(int i = 1; i < n; ++i){
int u, v; ll w;
scanf("%d%d%lld", &u, &v, &w);
g[u].push_back(P(v, w));
g[v].push_back(P(u, w));
}
dfs1(1, 0);
top[1] = 1;
dfs2(1);
//for(int i = 1; i <= n; ++i) cout<<":"<<dfn[i]<<endl;
return;
}
void add(int u, ll x){
while(u){
update(1, 1, n, id[top[u]], id[u], x);
ans[ fa[top[u]] ] += x*a[top[u]];
u = fa[top[u]];
}return;
}
ll Q(int u){
ll res = ((ll)qry(1, 1, n, id[1])-(ll)qry(1, 1, n, id[u]))*a[u];
if(son[u]){
res += (ll)qry(1, 1, n, id[son[u]])*a[son[u]];
}return res + ans[u];
}
void sol(){
int q; cin>>q;
int u = 1;
while(q--){
int op;scanf("%d", &op);
if(op == 1){
int u; ll x; scanf("%d%lld", &u, &x);
add(u, x);
}else scanf("%d", &u);
printf("%lld\n", Q(u));
}
}
int main()
{
init();
sol();
return 0;
}
7-8 2H. 叁佰爱抠的序列
可以转化为求最大的m,使得遍历m的完全图的每条边的步数+1小于等于n并输出方案。看到遍历每条边我们就可以想到欧拉路/回路。m为奇数,那么它的完全图每个点都是偶数,需要的步数为
,如果m是偶数,那么要出现至多2个奇数度数点,要加入额外
条边。可以证明它满足二分性质,直接二分找到答案之后跑欧拉回路算法就可以了。(i+=2 写成 ++i结果PE一下午)
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
ll check(ll lim){
if(lim&1){
return lim*(lim-1)/2+1;
}else{
return lim*(lim)/2;
}
}
struct node{
int v, nxt;
}e[20000005];
int vis[20000005];
int head[10000], cnt = 0;
void add(int u, int v){
e[cnt].v = v;
e[cnt].nxt = head[u];
head[u] = cnt++;
}
stack<int> s;
vector<int> path;
int du[10000];
void dfs(int u){//一次找一条路
while(du[u]){
int to = -1;int k = -1;
s.push(u);
for(int i = head[u];i!=-1;i=e[i].nxt){
if(vis[i]) continue;
if(to==-1&&k==-1){
to = e[i].v;k=i;
break;
}
}//寻找它的下一个可到达的最小的顶点
vis[k] = 1;vis[k^1] = 1;
du[u]--;du[to]--;
u = to;
}
s.push(u);
return;
}
void sol(ll n, ll N){
memset(head, -1, sizeof head);
for(int i = 1; i <= n; ++i) {
du[i] = n-1;
for(int j = i+1; j <= n; ++j) add(i,j), add(j, i);
}
if(n%2 == 0) {
for(int i = 2; i < n-1; i+=2) {
add(i, i+1), add(i+1, i);
du[i]++;
du[i+1]++;
}
}
dfs(1);
while(s.size()){
int x = s.top();s.pop();
if(du[x] == 0) path.push_back(x);
else dfs(x);
}
cout<<n<<"\n";
//cout<<"size:"<<path.size()<<endl;
cout<<path[0];
for(int i = 1; i < path.size(); ++i){
printf(" %d", path[i]);
//if(i != N-1) printf(" ");
}
for(int i = path.size(); i < N; ++i){
printf(" 1");
}printf("\n");
}
int main()
{
ll n;
cin>>n;
ll l = 1, r = 2e9;
ll m;
while(l <= r){
if(check(mid) <= n) m = mid, l = mid+1;
else r = mid-1;
}
if(n > 2000000){
cout<<m<<endl;;
}else{
sol(m, n);
}
}
7-11 2K. 破忒头的匿名信
可以考虑
为写完
的最小花费,那么有一个比较显然的转移:设
可以和原串s的
子串的后缀匹配,那么
.用AC自动机来快速得到当前后缀的每个可匹配串,暴力跳fail更新。因为字符串长度总和不超过
,所以不同长度的字符串个数是
级别,暴力跳fail是没问题的。
注:结构体写的AC自动机会赛场上疯狂TLE。赛后其他部分不改只修改为数组寻址就A了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e5 +50;
int n;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int fail[maxn];
int nxt[maxn][26];
ll cost[maxn];
int len[maxn];
int tot = 0;
int root;
void Insert(char *s, ll val){
int p = root;
int res = 0;
while(*s){
int x = *s - 'a';
if(!nxt[p][x]) {
nxt[p][x] = ++tot;
}
p = nxt[p][x];
s++;
res++;
}
if(cost[p])
cost[p] = min(cost[p], val);
else cost[p] = val;
len[p] = res;
}
char ch[maxn];
queue<int> q;
void get_fail()
{
while(q.size()) q.pop();
for(int i = 0; i < 26; ++i) if(nxt[root][i]) q.push(nxt[root][i]);
while(q.size()){
int cur = q.front();q.pop();
for(int i = 0; i < 26; ++i){
if(nxt[cur][i]) {
fail[ nxt[cur][i] ] = nxt[ fail[cur] ][i];
q.push(nxt[cur][i]);
}
else nxt[cur][i] = nxt[fail[cur]][i];
}
}
}
void init(){
root = tot = 0;
while(n--){
ll val;
scanf("%s%lld", ch, &val);
Insert(ch, val);
}
get_fail();
}
ll dp[maxn];
ll ac(char *s){
memset(dp, 0x3f, sizeof dp);
int p = root;
int n = strlen(s);
for(int i = 0; i < n; ++i){
int x = s[i] - 'a';
p = nxt[p][x];
int temp = p;
while(temp != root){
int pre = i-len[temp];
if(pre == -1) dp[i] = min(dp[i], cost[temp]);
else dp[i] = min(dp[i], dp[pre] + cost[temp]);
temp = fail[temp];
}
}
if(dp[n-1] == 0x3f3f3f3f3f3f3f3f) return -1;
else return dp[n-1];
//return ans;
}
int main(){
scanf("%d", &n);
init();
scanf("%s",ch);
cout<<ac(ch)<<endl;
}
/*
3
w
awdsa
zxca
awdsazxca
*/