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
*/