想看题的戳这里
A. 植物收集
难度:绿
先讲一下 \(O(n^3)\) 的暴力:
枚举一下要用多少个 \(k\)。将价格排序,假设要用 \(x\) 个 \(k\),则每个数会对其右边 \(x\) 个数产生贡献,按价格从小到大计算贡献。
优化一下,每次增加一个 \(k\),则每株植物最多往右边贡献 \(1\) 个,所以每次往右边枚举一个数,复杂度 \(O(n^2)\)。
还能优化!观察一下,发现对于每个 \(x*k\) 的贡献可以三分!再加上ST表,时间复杂度达到 \(O(n\ log\ n)\)。
#include<bits/stdc++.h>
#define ll long long
#define mxn 200010
using namespace std;
ll a[mxn],n,k,st[mxn][21],lg[mxn],ans;
void init(){
memset(st,0x7f,sizeof(st));
for(int i=1;i<=n;i++)lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
return;
}
ll get(ll x){
ll ret=0;
for(int i=x+1;i<=x+n/2;i++){
ll logg=lg[x+1]-1;
ret+=min(st[i-x][logg],st[i-(1<<logg)+1][logg]);
}
return ret;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("collect.in","r",stdin);
freopen("collect.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
ans+=a[i];
a[i+n]=a[i];
}
n*=2;
init();
ll l=0,r=n/2-1;
while(l<r){
ll lmid=l+(r-l)/3,rmid=l+(r-l)/3*2;
if(lmid==rmid)break;
if(get(lmid)+k*lmid>get(rmid)+k*rmid)l=lmid;
else r=rmid;
}
for(ll i=l;i<=r;i++)
ans=min(ans,get(i)+k*i);
cout<<ans;
return 0;
}
B. 美丽子区间
难度:绿
利用容斥原理,有:
美丽子区间数 = 区间数 - 最大值在开头的子区间数 - 最大值在结尾的子区间数 - 最小值在开头的子区间数 - 最小值在结尾的子区间数 + 最大值在开头最小值在结尾的子区间数量 + 最大值在结尾最小值在开头的子区间数量
对于减掉的部分,可以用单调栈计算;而加回去的部分,可以用树状数组解决,具体看代码。
#include<bits/stdc++.h>
#define ll long long
#define mxn 200010
using namespace std;
ll n,a[mxn],tre[mxn],ans;
ll big[mxn],sml[mxn],l[mxn],r[mxn];
ll stk[mxn],top;
vector<ll> lft[mxn];
void add(ll x){
while(x<=n)tre[x]++,x+=(x&-x);
return;
}
ll query(ll x){
ll ret=0;
while(x)ret+=tre[x],x-=(x&-x);
return ret;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("interval.in","r",stdin);
freopen("interval.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
ans=n*(n+1)/2;
for(int i=n;i;i--){
while(top&&a[stk[top]]<a[i])
big[stk[top]]=i,top--;
stk[++top]=i;
}
while(top)big[stk[top--]]=0;
for(int i=n;i;i--){
while(top&&a[stk[top]]>a[i])
sml[stk[top]]=i,top--;
stk[++top]=i;
}
while(top)sml[stk[top--]]=0;
for(int i=1;i<=n;i++)l[i]=min(sml[i],big[i])+1;
for(int i=1;i<=n;i++){
while(top&&a[stk[top]]<a[i])
big[stk[top]]=i,top--;
stk[++top]=i;
}
while(top)big[stk[top--]]=n+1;
for(int i=1;i<=n;i++){
while(top&&a[stk[top]]>a[i])
sml[stk[top]]=i,top--;
stk[++top]=i;
}
while(top)sml[stk[top--]]=n+1;
for(int i=1;i<=n;i++)r[i]=max(sml[i],big[i])-1;
for(int i=1;i<=n;i++)ans-=r[i]-l[i]+2;
for(int i=1;i<=n;i++)lft[l[i]].push_back(i);
for(int i=1;i<=n;i++){
for(int j=0;j<lft[i].size();j++)
add(lft[i][j]);
ans+=query(r[i])-query(i-1);
}
cout<<ans;
return 0;
}
C. 字符序列
难度:蓝
原题UOJ516。
神奇计数dp+矩阵乘法。
设 \(dp_{i,j}\) 为字符串 \(1\sim i\) 位,以字符 \(j\) 为结尾的子序列数,其中 \(j\) 为 \(a\sim z\)。
又设 \(lst_j\) 为字符 \(j\) 上一次出现的位置。
所以有 \(dp_{i,j}=\sum\limits_{k=a}^z dp_{lst_k,k}\)
发现这玩意是可以省一维的,即有 \(dp_i=\sum\limits_{k=a}^z dp_{lst_k}\)。
但是这样还不够,因为字符串长度会达到 \(2^n\) 级别,空间时间双爆炸。
设 \(t(i)\) 为进行依次进第 \(i\sim n\) 次操作得到的字符串,于是有 \(t(i)=t(i+1)+s_i+t(i+1)\)。
用一个矩阵乘法,可以达到 \(O(n)\) 级别。
#include<bits/stdc++.h>
#define ll long long
#define mxn 510
#define mod 1000000007
using namespace std;
ll n,ans;
char st[mxn];
struct matrix{
ll a[30][30];
void init(){
for(int i=0;i<=26;i++)
a[i][i]=1;
}
matrix(){memset(a,0,sizeof(a));}
};
matrix operator *(const matrix &a,const matrix &b){
matrix c;
for(int k=0;k<=26;k++)
for(int i=0;i<=26;i++)
for(int j=0;j<=26;j++)
c.a[i][j]=(a.a[i][k]*b.a[k][j]%mod+c.a[i][j])%mod;
return c;
}
matrix get(char c){
matrix ret;
for(int i=0;i<=26;i++){
ret.a[i][i]=1;
if(i==c-'a')
for(int j=0;j<=26;j++)
ret.a[i][j]=1;
}
return ret;
}
matrix res;
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("subseq.in","r",stdin);
freopen("subseq.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)cin>>st[i];
res.init();
for(int i=n;i;i--)res=res*get(st[i])*res;
for(int i=0;i<=25;i++)ans=(ans+res.a[i][26])%mod;
cout<<ans;
return 0;
}
D. 网络攻防
难度:蓝-紫
首先,\(k=1\) 的时候tarjan算割边数就行了。
对于 \(k=2\),我们先从这张图中抽出一棵生成树。这样有三种情况:
\(1.\) 删除两条非树边,贡献为 \(0\)。
\(2.\) 删一条树边,一条非树边。
若树边为桥边,则另外一条怎么选都行。
若不是,则该非树边应为生成树上唯一覆盖了这条边的非树边。
这里的覆盖是这样的:
其中,黑色边为树边,红色边为非树边,打勾的边是被覆盖的边。
\(3.\) 删两条树边。
若树边中存在桥边,无脑删就行了。
若不存在,则删除边 \(a,b\) 后图不连通的条件为覆盖了边 \(a\) 的非树边集 \(S_a\) 与覆盖了边 \(b\) 的非树边集 \(S_b\) 满足 \(S_a=S_b\)。可以自行手推一下。
于是,就有一个问题:怎么判断 \(S_a=S_b\)?对于这种边的相等问题,一般是用哈希解决。
#include<bits/stdc++.h>
#define ll long long
#define mxm 200010
#define mp make_pair
#define pll pair<long long,long long>
#define pb push_back
using namespace std;
ll n,m,k,val[mxm];
ll vis[mxm],edgevis[mxm];
ll istre[mxm],ans;
ll ndcnt[mxm],ndval[mxm];
ll sumcnt[mxm],sumval[mxm];
ll cnt_bridge,cnt_tre,cnt_cover1;
map<ll,ll> hsh;
vector<pll> t[mxm],tre[mxm];
void dfs1(int u){
vis[u]=1;
for(int i=0;i<t[u].size();i++){
int v=t[u][i].first,edgeid=t[u][i].second;
if(!vis[v]){
edgevis[edgeid]=istre[edgeid]=1;
tre[u].pb(mp(v,edgeid));
tre[v].pb(mp(u,edgeid));
dfs1(v);
}
else if(!edgevis[edgeid]){
edgevis[edgeid]=1;
ndcnt[u]++,ndcnt[v]--;
ndval[u]+=val[edgeid],ndval[v]-=val[edgeid];
}
}
return;
}
void dfs2(int u,int f){
sumcnt[u]=ndcnt[u],sumval[u]=ndval[u];
for(int i=0;i<tre[u].size();i++){
int v=tre[u][i].first,edgeid=tre[u][i].second;
if(v==f)continue;
dfs2(v,u);
sumcnt[u]+=sumcnt[v];
sumval[u]+=sumval[v];
if(sumcnt[v]==1)cnt_cover1++;//对于生成树中每条边,只被一条非树边覆盖的边的数量
if(sumval[v]==0)cnt_bridge++;//生成树中桥的数量
if(istre[edgeid])cnt_tre++;//生成树中边的数量
hsh[sumval[v]]++;
}
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("attack.in","r",stdin);
freopen("attack.out","w",stdout);
srand(time(0));
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
val[i]=rand();
t[u].pb(mp(v,i));
t[v].pb(mp(u,i));
}
dfs1(1);
dfs2(1,0);
hsh[0]=0;
if(k==2){
ans+=cnt_cover1;//一树一非(无桥)
ans+=cnt_bridge*(m-cnt_tre);//一树一非(有桥)
ans+=cnt_bridge*(cnt_tre-cnt_bridge);//两树(一桥一不桥)
ans+=cnt_bridge*(cnt_bridge-1)/2;//两树(两桥)
for(pll p:hsh)
ans+=p.second*(p.second-1)/2;//两树(无桥)
//注意这里的map是要用一个pair遍历的
}
cout<<ans+cnt_bridge;//加上k=1时的割边
return 0;
}
标签:cnt,20241010,int,ll,mxn,top,模拟,define
From: https://www.cnblogs.com/nagato--yuki/p/18455536