231017校内赛
T1 暴力操作
题解
确实非常暴力的一道题
首先非常明显的一点在于前半段没有任何贡献,所以只用考虑如何变小后面半段
很容易想到用二分答案来求最小值
那么该如何验证?
有一种错误的想法是算出最大能除几次,然后再每次除剩下一半中的最大值
发现明显不对,有些数要除多次且容易剩下一些花不完的钱
那么我们又可以想到对于一个数如果除多次,那么我们可以找到相应的次方的价格,看周围是否有更小价格的数
但是这样写对于各种条件很不好处理,换一种想法
对于每一个除数进行拆分,并与因数进行比较,求出最小花费
再与比它大的数比较,做一个后缀最小值
如果实现精细,那么这段的复杂度低于 \(\mathcal O(n\log n)\)
就可以很方便的求出每个数需要低于二分值的答案了
#include<bits/stdc++.h>
#define N 1000010
#define ll long long
using namespace std;
int n,m,k,a[N];
ll c[N];
inline ll check(int mid){
ll sum = 0;mid++;
for(int i = 1;i<=n;i++)
sum+=c[a[i]/mid+1];
return sum;
}
int main(){
freopen("opt.in","r",stdin);
freopen("opt.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i = 1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
for(int i = 1;i<=m;i++)
cin>>c[i];
c[1] = 0;
for(int i = 1;i<=m;i++)
for(int j = 1;j<=i&&i*j<=m;j++)
c[i*j] = min(c[i*j],c[i]+c[j]);
for(int i = m-1;i>=1;i--)
c[i] = min(c[i],c[i+1]);
c[m+1] = 1e18;
for(int i = 1;i<=m;i++){
int j = 1;
while(i*j<=m) j++;
if(i*j>m&&j<=m)
c[m+1] = min(c[i]+c[j],c[m+1]);
}
for(int i = m;i>=1;i--)
c[i] = min(c[i],c[i+1]);
n = (n+1)>>1;
int l = 0,r = a[n];
while(l<r){
int mid = l+r>>1;
if(check(mid)<=k) r = mid;
else l = mid+1;
}
cout<<r;
return 0;
}
T2 异或连通
题解
\(01 \ \ Trie\) 树
对于每一个询问二进制拆位构建一颗树,再在树上进行统计,方法如下这一部分我也不是很明白
注意到 \(1\) 条边只会在 \(\log K\) 段区间里存在, 类似使用线段树分治即可, 复杂度 $\mathcal O(n\log V\log n) $。
#include<bits/stdc++.h>
#define N 100010
#define int long long
using namespace std;
struct node{
int v,ne;
}e[N*30];
int n,m,q,k,cnt,tot = 1,sum,ch[N*30][2],h[N*30],ans[N*30];
int u[N],v[N],w[N],stk[N],top,fa[N],siz[N],pos[N];
void add(int x,int y){
e[++cnt].v = y;
e[cnt].ne = h[x];
h[x] = cnt;
}
int find_(int x){
return fa[x]==x?x:find_(fa[x]);
}
void union_(int x,int y){
int fx = find_(x),fy = find_(y);
if(fx==fy) return ;
if(siz[fx]>siz[fy]) swap(fx,fy);
fa[fx] = fy;
stk[++top] = fx;
sum+=siz[fx]*siz[fy];
siz[fy]+=siz[fx];
}
int insert(int x){
int p = 1;
for(int i = 29;i>=0;i--){
int y = (x>>i)&1;
if(!ch[p][y])
ch[p][y] = ++tot;
p = ch[p][y];
}
return p;
}
void backup(int x){
while(top!=x){
int u = stk[top--],v = fa[u];
siz[v]-=siz[u];
sum-=siz[v]*siz[u];
fa[u] = u;
}
}
void dfs(int x){
if(!x) return ;
int pre = top;
for(int i = h[x];i;i = e[i].ne)
union_(u[e[i].v],v[e[i].v]);
ans[x] = sum;
dfs(ch[x][0]);
dfs(ch[x][1]);
backup(pre);
}
signed main(){
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>q>>k;
for(int i = 1;i<=n;i++)
fa[i] = i,siz[i] = 1;
for(int i = 1;i<=m;i++)
cin>>u[i]>>v[i]>>w[i];
for(int i = 1;i<=q;i++){
int x;cin>>x;
pos[i] = insert(x);
}
for(int i = 1;i<=m;i++){
int p = (k^w[i]),now = 1;
for(int j = 29;j>=0;j--){
int x = (p>>j)&1;
if((k>>j)&1) add(ch[now][x^1],i);
now = ch[now][x];
if(!now) break;
}
}
dfs(1);
for(int i = 1;i<=q;i++)
cout<<ans[pos[i]]<<"\n";
return 0;
}
T3 诡异键盘
题解
一开始可能会考虑 \(AC\) 自动机,但很明显是不对的,复杂度直接爆炸
那么考虑 \(dp\) ,我们需要求出打印出 \(i\) 向后到 \(j\) 需要多少次操作
即我们选择一个 \(S_p\), 将 \(S_p\) 打出, 然后删到只剩 \(S[i \ldots j]\) 这段
我们建立 \(trie\) 树, 枚举 \([i,j]\),暴力在子树中找一个 \(S_i\),这样只需要求出删掉 \(\left| S_i \right| − (j−i+1)\) 个字符的最小次数
这样 \(DP\) 复杂度为 $\mathcal O(\sum\left|S_i\right| +\left| S \right| ^2) $一个 \(trie\) 树节点 的答案只需求一次
容易发现只需要预处理删掉 \([1,K)\) 个字符的最小次数,这是一个类似“同余”最短路,可以在 \(\mathcal O(K^2)\) 的时间解决
由于本质不同的长度为 \(n\) ,所以复杂度其实是 \(\mathcal O( \sqrt{nT}K)\).
#include<bits/stdc++.h>
#define int long long
#define N 5010
#define inf (2e9)
#define M 1000100
using namespace std;
int n,k,cnt = 1,f[N],ch[M][30],fa[M],ans[M],pos[M],val[N],dis[N],que[N],tot = 0;
bool vis[N];
int insert(string s){
int now = 1;
for(int i = 0;i<s.size();i++){
if(!ch[now][s[i]-'a']){
ch[now][s[i]-'a'] = ++cnt;
fa[cnt] = now;
}
now = ch[now][s[i]-'a'];
}
return now;
}
void bfs(){
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[0] = 0;
for(int i = 0;i<k;i++){
int u = -1;
for(int j = 0;j<k;j++)
if(!vis[j]&&(u==-1||dis[j]<dis[u])) u = j;
vis[u] = 1;
for(int j = 1;j<=tot;j++)
if(dis[u]+val[que[j]]<dis[(u+que[j])%k])
dis[(u+que[j])%k] = dis[u]+val[que[j]];
}
return ;
}
int gt(int x){
int tmp = k-x%k;
if(tmp>=k) tmp-=k;
if(dis[tmp]>=inf) return inf;
return (x+dis[tmp])/k;
}
signed main(){
freopen("keyboard.in","r",stdin);
freopen("keyboard.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--){
memset(val,0x3f,sizeof(val));
tot = 0;
for(int i = 1;i<=cnt;i++){
memset(ch[i],0,sizeof(ch[i]));
fa[i] = 0;
}
cnt = 1;
cin>>n>>k;string s;
for(int i = 1;i<=n;i++){
cin>>s;
int tmp = s.size();
val[tmp%k] = min(val[tmp%k],tmp+k);
pos[i] = insert(s);
}
for(int i = 0;i<k;i++)
if(val[i]<inf) que[++tot] = i;
bfs();
memset(ans,0x3f,sizeof(ans));
for(int i = 1;i<=n;i++){
int now = pos[i],tmp = 0;
while(now!=1){
ans[now] = min(ans[now],gt(tmp)+1);
tmp++;
now = fa[now];
}
}
cin>>s;int l = s.size();
f[l] = 0;
for(int i = l-1;i>=0;i--){
int now = 1;
f[i] = inf;
for(int j = i;j<l;j++){
now = ch[now][s[j]-'a'];
if(!now) break;
f[i] = min(f[i],f[j+1]+ans[now]);
}
}
if(f[0]>=inf) cout<<"-1\n";
else cout<<f[0]<<"\n";
}
return 0;
}
T4 民主投票
题解
结果 T4 最简单了
一道树形 \(dp\) 题
这道题每个点最少需要的票数来成为最多是确定的,我们可以一开始就处理出来 \(min\)
考虑 $ f_{x,s} $ 表示当前子树 \(x\) 有 \(s\) 票时这个内部消化多少票,用二分来求出其他点是否可以满足小于 \(s-1\) 的条件
那么对于子树大小 \(size > min\) 的点一定是可以的,对于 \(size<min\) 的点一定不可以
只有一个 \(size==min\) 需要考虑
那么当 \(f_{1,min} = 0\) 时,每个点都不会超过当前 \(min\)
那么我们考虑先用 \(min-1\) 求出一个答案,如果 \(f_{x,min-1}=0\) 时,\(f_{1,min} = 1\) 显然不能获胜
如果\(f_{x,min-1}\) 到根节点上所有点都为 \(1\) ,那么 \(f_{1,min-1}=0\) ,所以是可以获胜的
总感觉说的不是太清楚,你们看代码理解一下
#include<bits/stdc++.h>
#define N 1001000
using namespace std;
int n,fa[N],siz[N],tmp[N],ans[N];
bool check(int mid){
for(int i = 1;i<=n;i++) tmp[i] = 0;
for(int i = n;i>=2;i--){
tmp[fa[i]]++;
if(tmp[i]>mid) tmp[fa[i]]+=tmp[i]-mid;
}
return tmp[1]<=mid;
}
int main(){
freopen("election.in","r",stdin);
freopen("election.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--){
cin>>n;
for(int i = 2;i<=n;i++)
cin>>fa[i];
int l = 1,r = n,s = -1;
while(l<=r){
int mid = l+r>>1;
if(check(mid)) s = mid,r = mid-1;
else l = mid+1;
}
for(int i = 1;i<=n;i++) ans[i] = siz[i] = 0;
for(int i = n;i>=2;i--)
siz[fa[i]]+=siz[i]+1;
for(int i = 1;i<=n;i++){
if(siz[i]>s) ans[i] = 1;
else if(siz[i]<s) ans[i] = 0;
}
for(int i = 1;i<=n;i++) tmp[i] = 0;
for(int i = n;i>=2;i--){
tmp[fa[i]]++;
if(tmp[i]>s-1) tmp[fa[i]]+=tmp[i]-s+1;
}
for(int i = 2;i<=n;i++) tmp[i] = min(tmp[i],tmp[fa[i]]);
for(int i = 2;i<=n;i++)
if(tmp[1]==s&&tmp[i]==s&&siz[i]==s)
ans[i] = 1;
ans[1] = 1;
for(int i = 1;i<=n;i++)
cout<<ans[i];
cout<<"\n";
}
return 0;
}
梦与现实间挣扎着,所求为何
你可以借走我的文章,但你借不走我的智慧 虽然我是傻逼本文来自博客园,作者:cztq,转载请注明原文链接:https://www.cnblogs.com/cztq/p/17775535.html