题面
写在前面:总算正常发挥了一次没有出降智错误,rank 4,好耶!
A.分割
DP+前缀和+排序+离散化+树状数组优化
(f_i) 是以i结尾的方案数,要注意 (sum_i=0) 的时候 (f_i) 也有一种方案。
(code)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
const int mod=1000000009;
int n,x;
int b[maxn],t[maxn],f[maxn];
struct data{
int sum;
int id;
}a[maxn];
bool cmp(data x,data y){
if(x.sum!=y.sum) return x.sum<y.sum;
return x.id<y.id;
}
void add(int x,int k){
for(;x<=n;x+=x&-x) (b[x]+=k)%=mod;
}
int ask(int x){
int res=0;
for(;x;x-=x&-x) (res+=b[x])%=mod;
return res;
}
signed main(){
read(n);
for(int i=1;i<=n;i++){
read(x);
a[i].sum=a[i-1].sum+x;
a[i].id=i;
if(a[i].sum>=0) f[i]=1;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++) t[a[i].id]=i;
for(int i=1;i<=n;i++){
(f[i]+=ask(t[i]))%=mod;
add(t[i],f[i]);
}
printf("%lld
",f[n]);
return 0;
}
/*
4
2
3
-3
1
//
4
*/
B.子序列
一开始求成子序列的数值之和了没注意到是权值和
最后写了30pts的暴搜,过了样例1,样例2过不去,一边写别的题一边让它接着跑样例2,到了比赛结束跑了两个半小时都没跑完...中午吃饭忘了关程序,回来发现还在跑...跑了五个小时愣是没跑完 (n=1000) 的样例2...事实证明指数级的dfs果然慢的一批这不废话吗
先放一个能跑一年的暴搜:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
const int mod=10007;
int n,a[maxn];
int cnt,k,mp[maxn],tong[maxn],ok[maxn],sum1,sum2;
int qpow(int x,int y){
int res=1;
for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod;
return res%mod;
}
void dfs(int *b,int siz,int *ok,int dep){
if(siz==dep){
int cnt=0;
bool flag=0;
memset(mp,0,sizeof(mp));k=0;
for(int i=0;i<siz;i++){
// cout<<b[i]<<"***"<<ok[i]<<" ";
if(ok[i]){
mp[++k]=i;
if(!tong[b[i]]) tong[b[i]]=1,cnt++;
}
}
// cout<<endl;
sum1=(sum1+cnt)%mod;
// cout<<sum1<<"*"<<endl;
// cout<<mp[1]<<" ";
for(int i=2;i<=k;i++){
if(mp[i-1]+1!=mp[i]) flag=1;
// cout<<mp[i]<<" ";
}
// cout<<endl;
if(!flag) sum2=(sum2+cnt)%mod;
// cout<<sum2<<"**"<<endl;
for(int i=0;i<siz;i++){
if(tong[b[i]]) tong[b[i]]=0;
}
// for(int i=0;i<siz;i++){
// if(ok[i]) printf("%lld ",b[i]);
// }
// printf("
");
// cout<<"********"<<endl;
return ;
}
else{
ok[dep]=0;
dfs(b,siz,ok,dep+1);
ok[dep]=1;
dfs(b,siz,ok,dep+1);
}
}
signed main(){
// freopen("ex_seq2.in","r",stdin);
read(n);
for(int i=0;i<n;i++) read(a[i]);
dfs(a,n,ok,0);
cout<<sum1<<" "<<sum2<<endl;
return 0;
}
/*
3
2 3 2
//
10 9
*/
上正解
(O(n)) 枚举位置,把它变成计数类问题
第一个问题:求所有子序列的权值和
考虑当前这位数对答案的贡献
所以这个数必须在这个序列中第一次出现,若它前面已经有一样的数了那必然会去重,而当前这个数就是被去掉的那个,对答案就没有贡献了
我们开个桶 (c) 来记录第 (i) 位前面有多少个值和它相同的数,它贡献的答案就是 (2^{n-c_i})
第二个问题:求所有连续子序列的权值和
记 (l_i) 为 (i) 前面与它最后一个相同的数的位置,贡献的答案为 ((n-i+1)*(i-l_i))
因为 (a_i) 可能很大所以需要离散化
(code)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
#define pii pair<long long,long long>
#define fir first
#define sec second
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
const int mod=10007;
int n;
pii a[maxn];
int c[maxn],num[maxn],l[maxn],p[maxn],sum1,sum2,cnt;
bool cmp(pii x,pii y){
return x.fir<y.fir;
}
signed main(){
read(n);
for(int i=1;i<=n;i++){
read(a[i].fir);
a[i].sec=i;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
if(a[i-1].fir!=a[i].fir) cnt++;
num[a[i].sec]=cnt;
}
p[0]=1;
for(int i=1;i<=n;i++) p[i]=(2*p[i-1])%mod;
for(int i=1;i<=n;i++){
c[num[i]]++;
(sum1+=p[n-c[num[i]]])%=mod;
(sum2+=(n-i+1)*(i-l[num[i]])%mod)%=mod;
l[num[i]]=i;
}
cout<<sum1<<" "<<sum2<<endl;
return 0;
}
/*
3
2 3 2
//
10 9
*/
C.昊昊的故事1之昊昊的崛起
果然只会敲暴力
定义 (d(x,y)) 为 (x,y) 两点间路径上最小边权
因为求的是
所以很容易想到求LCA,我们可以用树上倍增处理
但是写的极长还一度以为自己写假了差点自闭
60pts代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
#define maxn 500010
//#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
const int inf=0x3f3f3f3f;
int n;
int f[maxn][25],dp[maxn][25],p[maxn],dep[maxn],vis[maxn],sum,tmp,ans;
int cnt=1,head[maxn];
struct node{
int to;
int w;
};
vector<node> g[maxn];
struct edge{
int x;
int y;
int w;
}e[2*maxn];
bool cmp(edge a,edge b){
return a.w>b.w;
}
int find(int x){
return x==p[x]?x:find(p[x]);
}
void init(){
for(int i=0;i<=n;i++) p[i]=i;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
dp[i][j]=inf;
}
}
}
void pre(){
int tot=0;
for(int i=1;i<=n-1;i++){
int x=e[i].x,y=e[i].y,w=e[i].w;
int dx=find(x),dy=find(y);
if(dx!=dy){
p[dx]=dy;
node b;
b.to=y;
b.w=w;
g[x].push_back(b);
b.to=x;
g[y].push_back(b);
tot++;
if(tot>=n-1) break;
}
}
}
void dfs(int u,int fa,int step,int w){
vis[u]=1;
if(fa) f[u][0]=fa,dp[u][0]=w;
dep[u]=step;
int len=g[u].size();
for(int i=0;i<len;i++){
int to=g[u][i].to;
if(to==fa)continue;
dfs(to,u,step+1,g[u][i].w);
}
}
void work(){
for(int j=1;j<=19;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
}
}
for(int j=1;j<=19;j++){
for(int i=1;i<=n;i++){
dp[i][j]=min(dp[i][j-1],dp[f[i][j-1]][j-1]);
}
}
}
int lca(int x,int y){
int res=inf;
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
res=min(res,dp[x][i]);
x=f[x][i];
}
}
if(x==y) return res;
for(int i=19;i>=0;i--){
if(f[x][i]!=f[y][i]){
res=min(res,min(dp[x][i],dp[y][i]));
x=f[x][i],y=f[y][i];
}
}
res=min(res,min(dp[x][0],dp[y][0]));
return res;
}
int main(){
read(n);
for(int i=1;i<=n-1;i++)read(e[i].x),read(e[i].y),read(e[i].w);
sort(e+1,e+n-1+1,cmp);
init();
pre();
for(int i=1;i<=n;i++){
if(!vis[i]) dfs(i,0,1,inf);
}
work();
for(int i=1;i<=n;i++){
sum=0;
for(int j=1;j<=n;j++){
if(i!=j){
sum+=lca(i,j);
}
}
ans=max(ans,sum);
}
printf("%d
",ans);
return 0;
}
/*
4
1 2 2
2 4 1
2 3 1
//
4
*/
正解是并查集
将边权从大到小排序,设当前边为 ((x,y,w)) ,假设有两个连通块,左边的最优值是 (A) ,右边的最优值是 (B),(x subset A),(y subset B)。通过当前的边使左右联通, 最大值就是 (max(A+siz_y*w,B+siz_x*w))
由此得出用并查集维护,每次合并时执行求最大值的操作,这样合并到最后就是答案
(code)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
#define maxn 500010
//#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n;
int fa[maxn],d[maxn];
struct node{
int x;
int y;
int w;
}e[2*maxn];
//struct ST{
// int x;
// int y;
// int w;
// int sizx;
// int sizy;
//}st[maxn];
bool cmp(node a,node b){
return a.w>b.w;
}
struct bcj{
int fa[maxn],siz[maxn];
void init(int n){
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
}
int find(int x){
return (x==fa[x])?x:fa[x]=find(fa[x]);
}
void u(int x,int y,int w){
x=find(x),y=find(y);
if(x==y) return ;
if(siz[x]>siz[y]) swap(x,y);
d[y]=max(d[x]+siz[y]*w,d[y]+siz[x]*w);
siz[y]+=siz[x];
fa[x]=y;
}
}s;
int main(){
read(n);
for(int i=1;i<=n-1;i++) read(e[i].x),read(e[i].y),read(e[i].w);
s.init(n);
sort(e+1,e+n-1+1,cmp);
for(int i=1;i<=n-1;i++){
s.u(e[i].x,e[i].y,e[i].w);
}
cout<<d[s.find(1)]<<endl;
return 0;
}
/*
4
1 2 2
2 4 1
2 3 1
//
4
*/
D.机房的人民摸鱼家
太ex了细节太多了
不会,没写,先咕了