今日得分:100+10+0。(T2T3有点毒瘤,但T3代码意外好写)
T1
题目大意:给你一张n个点n条边的无向图,满足每条边形如(i,t[i],w[i]),两人轮流操作,每次可以选择一条未被选择的边,要求选择后已选择的边不能出现环,直到不能操作为止。先手希望选择的边权值之和最小,后手希望选择的边权值之和最大,求最终选择的边的权值最大。n<=1e5,t1<=[i]<=n,w[i]<=1e6。
题解:容易发现,每条边最多在一个简单环中,且最终一定是在每个环中找出一条边不选,其余的边全选。于是我们可以将原图中每个环找出来,对于不在环中的边直接加入答案,对于长度为奇数的环,一定是不选权值排中间的那条边;对于长度为偶数的环,一定是不选权值排中间的两条边之一,且两人选择的顺序为按照这两条边的差值排序后尽可能大的对自己有利的边,直接做即可。时间复杂度O(nlogn)(排序复杂度)。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x*f;
}
inline int mi(int x,int y){return x<y?x:y;}
int n;
int t[100010],w[100010];
vector<int> g[100010];
long long ans;
int dfn[100010],low[100010],col[100010],sum,tot;
int z[100010];
bool fla[100010];
void tarjan(int x)
{
fla[x]=true;dfn[x]=low[x]=++tot;z[++z[0]]=x;
register int i,sz=g[x].size(),v;
for(i=0;i<sz;++i)
{
v=g[x][i];
if(!dfn[v]){tarjan(v);low[x]=mi(low[x],low[v]);}
else if(fla[v])low[x]=mi(low[x],dfn[v]);
}
if(dfn[x]==low[x])
{
register int y=z[z[0]];++sum;
while(y!=x){col[y]=sum;fla[y]=false;y=z[--z[0]];}col[z[z[0]--]]=sum;fla[x]=false;
}
}
vector<int> bian[100010];
int a[100010],kx[100010],tt;
void solve()
{
register int i,j,sz,to;
for(i=1;i<=sum;++i)if(!bian[i].empty())
{
sz=bian[i].size();
for(j=0;j<sz;++j)a[j+1]=bian[i][j];
sort(a+1,a+sz+1);
if(sz&1)
{
to=sz>>1;
for(j=1;j<=to;++j)ans+=a[j];for(j=to+2;j<=sz;++j)ans+=a[j];
}
else
{
to=sz>>1;
for(j=1;j<=to;++j)ans+=a[j];for(j=to+2;j<=sz;++j)ans+=a[j];
kx[++tt]=a[to+1]-a[to];
}
}
sort(kx+1,kx+tt+1);
for(i=tt-1;i>=1;i-=2)ans+=kx[i];
}
int main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
register int i;
n=re_ad();
for(i=1;i<=n;++i){t[i]=re_ad();g[i].push_back(t[i]);}
for(i=1;i<=n;++i)w[i]=re_ad();
for(i=1;i<=n;++i)if(!dfn[i])tarjan(i);
for(i=1;i<=n;++i)
{
if(col[i]==col[t[i]])bian[col[i]].push_back(w[i]);
else ans+=w[i];
}
solve();
cout<<ans<<endl;
return 0;
}
T2
题目大意:给你一个n个点的树,求在这棵树中任意选出m个点,最多能有多少个点与这m个点的距离都<=d。1<=m<=n<=1e6,0<=d<=n。
题解:首先显然所有哨塔应该构成一个连通块。 然后对于一个大小为m的连通块,考虑它的直径中点,设直径长度为r,那么要求dis<=r的点个数>=m,并且次长链长度>=r,那么距离不超过d-r的点的个数就是答案了。长链剖分+换根即可维护每一个点为根的某个深度以下的点的个数。 注意到父亲节点的r与当前节点的r差不超过1,因此可以做到O(n)。
T3
题目大意:定义一个长度为n的序列A是合法的,当且仅当1<=a1<=a2<=…<=an<=n,且对任意k满足1<=k<n,从A中取出任意k+1个数的和严格大于从A中取出任意k个数的和。给定n,求合法的A的个数对输入的质数p取模的结果。n<=333333,9e8<=p<1e9。
题解:
n^2dp代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^48),ch=getchar();}
return x*f;
}
int n,p,m;
int dp[33350],ans;
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
register int i,j,x;
n=re_ad();p=re_ad();
dp[0]=1;m=(n>>1)+1;
for(i=2;i<=n;++i)
{
if(i<=m)x=i-1;else x=n-i+2;
for(j=x;j<=n;++j)dp[j]=(dp[j]+dp[j-x])%p;
}
for(i=0;i<=n;++i)ans=(ans+1ll*(n-i)*dp[i]%p)%p;
cout<<ans<<endl;return 0;
}
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^48),ch=getchar();}
return x*f;
}
int n,p,mo,m;
inline int jian(int x,int y){x-=y;if(x<0)x+=mo;if(x>=mo)x-=mo;return x;}
int F[333350],ans,G[333350],num[333350],cnt;
inline void getinv()
{
register int i,j;
for(i=1;i<n;++i)for(j=1;num[j]<=i&&j<=cnt;++j){F[i]=jian(F[i],F[i-num[j]]*G[j]);}
}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
register int i,j,x;
n=re_ad();p=re_ad();m=(n+1)>>1;mo=p;
for(i=m+1;i<n;++i)F[i]=p-2;
G[0]=1;
for(i=1;i*(3*i-1)<(n<<1);++i)
{
if(i*(3*i-1)<(n<<1))num[++cnt]=i*(3*i-1)>>1,G[cnt]=(i&1)?-1:1;
if(i*(3*i+1)<(n<<1))num[++cnt]=i*(3*i+1)>>1,G[cnt]=(i&1)?-1:1;
}
F[0]=1;
getinv();getinv();
for(i=n-1;i>=1;--i)F[i]=jian(F[i],F[i-1]);
if(n&1)for(i=n-1;i>=m;--i)F[i]=jian(F[i],F[i-m]);
for(i=0;i<n;++i)ans=(ans+1ll*(n-i)*F[i]%p)%p;
cout<<ans<<endl;return 0;
}
(附:某大佬该题的题解)