连着两次考试小于机房平均分了/kk,不努点力看来是不行了。


T1

将图分成一个团和一个独立集的方案数,正解是爆搜/fad。

然而把图建出来反而不好搜,于是不建图,只枚举每个点在团中还是在独立集中。这样看起来是 \(O(2^N)\) ,但实际上可以剪枝剪掉大部分不合法方案。

考场上时间没分配好,打了2h暴力无果。

考后bitset爆艹标算

\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <bitset>
using namespace std;
typedef long long lxl;
const int maxn=1e3+5,maxm=maxn*maxn;
const lxl mod=1000003;

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline void read(T &x)
{
	x=0;T f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	x*=f;
}

int n,m;
bool vis[maxn];
bitset<maxn> bes[maxn],worker,part;
int ans;

inline void dfs(int x)
{
	if(x==n+1) return ans+=(worker.any()&&part.any()),void();
	if((worker|bes[x])==bes[x])
	{
		worker.set(x);
		dfs(x+1);
		worker.reset(x);
	}
	if(!(part&bes[x]).any())
	{
		part.set(x);
		dfs(x+1);
		part.reset(x);
	}
}

int main()
{
	// freopen("party.in","r",stdin);
	// freopen("party.out","w",stdout);
	int T;read(T);
	while(T--)
	{
		read(n),read(m);
		for(int i=1;i<=n;++i) bes[i].reset();
		worker.reset();part.reset();
		for(int i=1,u,v;i<=m;++i)
		{
			read(u),read(v);
			bes[u].set(v);
			bes[v].set(u);
		}
		ans=0;
		dfs(1);
		printf("%d\n",ans);
	}
	return 0;
}

T2

枚举小N选取的前缀,发现小A一定会选取最大后缀,那么直接上线段树就好了。

但是线段树用在这道题上是多余的,正解复杂度是 \(O(n)\)

T1打了2h暴力无果后,开了T2,花了不到1h过了T2。

\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <climits>
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
const int maxn=1e6+5;

template <typename T>
inline void read(T &x)
{
	x=0;T f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	x*=f;
}

int n;
lxl A,B,a[maxn];

struct node
{
	int l,r;
	lxl rmax,sum;
	node(int l,int r,lxl rmax,lxl sum)
		:l(l),r(r),rmax(rmax),sum(sum){}
	node(){}
	inline node operator + (const node &T)const
	{
		return node(l,T.r,max(T.rmax,rmax+T.sum),sum+T.sum);
	}
};

struct Segment_Tree
{
	node tree[maxn<<2];
	#define ls (p<<1)
	#define rs (p<<1|1)
	inline void init(int p,lxl d)
	{
		tree[p].sum=d;
		tree[p].rmax=max(d,0ll);
	}
	void build(int p,int l,int r)
	{
		tree[p]=node(l,r,0,0);
		if(l==r) return init(p,a[l]),void();
		int mid=(l+r)>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		tree[p]=tree[ls]+tree[rs];
	}
	void modify(int p,int ps,lxl d)
	{
		int l=tree[p].l,r=tree[p].r;
		if(l==r) return init(p,d),void();
		int mid=(l+r)>>1;
		if(ps<=mid) modify(ls,ps,d);
		else modify(rs,ps,d);
		tree[p]=tree[ls]+tree[rs];
	}
}st;

int main()
{
	// freopen("game.in","r",stdin);
	// freopen("game.out","w",stdout);
	read(n),read(A),read(B);
	for(int i=1;i<=n;++i)
		read(a[i]);
	st.build(1,1,n);
	lxl ans=LLONG_MIN;
	for(int i=0;i<=n;++i)
	{
		if(i>=1) st.modify(1,i,a[i]*=-A);
		node res=st.tree[1];
		ans=max(ans,res.sum-res.rmax-res.rmax*B);
//		res=st.tree[1];
	}
	printf("%lld\n",ans);
	return 0;
}

T3

第一轮的困扰值可以直接放在边上。

第二轮也可以放在边上。考虑边 \(u\leftrightarrow v\) 对它两端点的影响:

  • 若先在 \(u\) 上放一个居民,再在 \(v\) 上放一个居民,则困扰值为 \(F_u\times T_v+F_v\times (T_u+1)\)
  • 若先在 \(v\) 上放一个居民,再在 \(u\) 上放一个居民,则困扰值为 \(F_v\times T_u+F_u\times (T_v+1)\)

将两式相减可以发现:先在 \(F\) 值大的城市上放居民的困扰值较小。于是可以贪心地放居民,由于决策只与 \(F_u\)\(F_v\) 的大小有关,不难发现,最优的方案是先将 \(F\) 值大的城市放满居民,再放另一个城市。若 \(F_u>F_v\) ,则这条边造成的困扰值为:

\[F_u\times(U_u-T_u)\times T_v+F_v\times U_u\times (U_v-T_v) \]

否则 \(F_u<F_v\) 同理。

建出图跑最小生成树即可。

考试最后30min才开T3,所以没怎么思考,不过题解的这种转化思想是ao的,还是有收获。

\(\text{Code}:\)

#include <cstring>
#include <cstdio>
#include <algorithm>
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
const int maxn=55;

template <typename T>
inline void read(T &x)
{
	x=0;T f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	x*=f;
}

struct edge
{
	int u,v;
	lxl w;
	edge(int u,int v,lxl w):u(u),v(v),w(w){}
	edge(){}
	inline bool operator < (const edge &T)const
	{
		return w<T.w;
	}
}e[maxn*maxn];
int ecnt;

int n,fa[maxn];
lxl T[maxn],U[maxn],F[maxn],C,ans;
char G[maxn][maxn];

inline int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}

inline lxl calcu(int i,int j)
{
	if(F[i]<F[j]) swap(i,j);
	return F[i]*(U[i]-T[i])*T[j]+F[j]*U[i]*(U[j]-T[j]);
}

inline bool merge(int u,int v)
{
	int x=find(u),y=find(v);
	if(x==y) return false;
	fa[x]=y;
	return true;
}

int main()
{
	// freopen("reconstruction.in","r",stdin);
	// freopen("reconstruction.out","w",stdout);
	read(n);
	for(int i=1;i<=n;++i) read(T[i]);
	for(int i=1;i<=n;++i) read(U[i]);
	for(int i=1;i<=n;++i) read(F[i]);
	for(int i=1;i<=n;++i)
		scanf(" %s\n",G[i]+1);
	for(int i=1;i<=n;++i)
		fa[i]=i,ans+=(U[i]-T[i])*(T[i]+U[i]-1)/2ll*F[i];
	read(C);
	for(int i=1;i<=n;++i)
		for(int j=i+1;j<=n;++j)
			if(G[i][j]=='Y')
			{
				ans+=calcu(i,j);
				merge(i,j);
			}
			else e[++ecnt]=edge(i,j,calcu(i,j)+(T[i]+T[j])*C);
	sort(e+1,e+ecnt+1);
	for(int i=1;i<=ecnt;++i)
	{
		int u=e[i].u,v=e[i].v;
		ans+=e[i].w*merge(u,v);
	}
	printf("%lld\n",ans);
	return 0;
}