分没了,rating也没留着。


T1

只需要开方5次,所以可以找到一个分界点,使得它恰好只能开方5次。通过二分,可以算出这个分界点是 \(2^{32}\)

考试时前1h看错了题,以为是开6次,结果找出了一个很大的数做分界点。最后30min发现题读错了,慌慌张张地重新找了分界点,结果找成了 \(2^{32}-1\) ……

\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long lxl;

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;
}

char num[105];
lxl lim=4294967296ll;

inline void check(lxl x)
{
	if(x==1) return puts("0"),void();
	if(!x) return puts("TAT"),void();
	for(int i=1;i<6;++i)
	{
		x=sqrt(x);
		if(x==1) return printf("%d\n",i),void();
	}
	puts("TAT");
	return;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("sqrt.in","r",stdin);
	freopen("sqrt.out","w",stdout);
#endif
	while(~scanf(" %s",num+1))
	{
		lxl x=0;
		int len=strlen(num+1);
		bool flag=true;
		for(int i=1;i<=len;++i)
		{
			x=x*10ll+1ll*num[i]-1ll*'0';
			if(x>=lim) {flag=false;break;}
		}
		if(!flag) {puts("TAT");continue;}
		check(x);
	}
	return 0;
}

T2

先对完全图做一次最小生成树,再枚举选哪些多边形,只使用原图的最小生成树的边做最小生成树。

至于为什么是对的,假设一个多边形只包含两个点,把它加入图中相当于连接了这两个点,加入这个多边形后就可以删去原树上以这两个点为端点的链上的最大的边。若多边形包含多个点,可以看作若干个只包含两个点的多边形,也就可以扩展成上面的情况。

容易发现这样做只会删边,不会加入新的边,所以新图的最小生成树的边集一定是原图的最小生成树的边集的子集。

\(\text{Code}:\)

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

#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;

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],kru[maxn*maxn];
int ecnt,kcnt;

struct point
{
	int x,y;
	point(int x,int y):x(x),y(y){}
	point(){}
	inline lxl operator + (const point &T)const
	{
		return 1ll*(x-T.x)*(x-T.x)+1ll*(y-T.y)*(y-T.y);
	}
}p[maxn];

vector<int> sp[11];
vector<int>::iterator it;
lxl cst[11];
int scnt;

int fa[maxn],gap[maxn];

inline int find(int x)
{
	if(fa[x]==x) return x;
	--gap[fa[x]];
	++gap[fa[x]=find(fa[x])];
	return fa[x];
}

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

lxl ans=LLONG_MAX,res;

inline void Kruskal()
{
	for(int i=1;i<=ecnt;++i)
	{
		int u=e[i].u,v=e[i].v;
		if(merge(u,v))
			res+=e[i].w;
		if(res>=ans) return;
		if(gap[fa[u]]==n) break;
	}
	ans=min(ans,res);
}

inline void solve(int S)
{
	for(int i=1;i<=n;++i) fa[i]=i,gap[i]=1;
	res=0;
	for(int i=0;i<scnt;++i)
		if(S&(1<<i))
		{
			res+=cst[i];
			it=sp[i].begin();
			while(++it!=sp[i].end())
				merge(*it,*sp[i].begin());
		}
	Kruskal();
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
#endif
	read(n),read(m);
	for(int i=1,x,y;i<=n;++i)
	{
		read(x),read(y);
		p[i]=point(x,y);
	}
	for(int i=0,k,c,x;i<m;++i)
	{
		read(k),read(c);
		if(!k) continue;
		cst[scnt++]=c;
		while(k--)
		{
			read(x);
			sp[i].push_back(x);
		}
	}
	for(int i=1;i<=n;++i)
		for(int j=i+1;j<=n;++j)
			kru[++kcnt]=edge(i,j,p[i]+p[j]);
	sort(kru+1,kru+kcnt+1);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=kcnt;++i)
	{
		int u=kru[i].u,v=kru[i].v;
		if(merge(u,v)) e[++ecnt]=edge(u,v,kru[i].w);
	}
	for(int i=0;i<(1<<scnt);++i)
		solve(i);
	printf("%lld\n",ans);
	return 0;
}

T3

先将所有连续的相同颜色的数缩在一起,形成一个新序列,设新序列第 \(i\) 个元素代表的颜色是 \(color_i\) ,由 \(len_i\) 个原序列中的数缩在一起。

考虑区间DP,令 \(f_{i,j,k}\) 表示区间 \([i,j]\) 和紧跟在区间后面有 \(k\) 个颜色为 \(color_j\) 的数一起最大有多少贡献,分两种情况转移:

  • 将后面的颜色为 \(color_j\)\(len_j+k\) 个数消掉,\(f_{i,j,k}=f_{i,j-1,0}+(len_j+k)^2\)
  • 找到区间内一个位置 \(p\) 使得 \(color_p=color_j\) ,将区间 \([p+1,j-1]\) 消掉使得 \(p\)\(j\) 两段拼在一起,\(f_{i,j,k}=f_{i,p,k+len_j}+f_{p+1,j-1,0}\)

复杂度 \(O(n^3)\)

\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long lxl;
const int maxn=3e2+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,m,a[maxn];
int f[maxn][maxn][maxn];
int len[maxn],color[maxn];

int dp(int l,int r,int k)
{
	if(l>r) return f[l][r][k]=0;
	if(~f[l][r][k]) return f[l][r][k];
	int res=dp(l,r-1,0)+(len[r]+k)*(len[r]+k);
	for(int p=l;p<r;++p) if(color[p]==color[r])
		res=max(res,dp(l,p,len[r]+k)+dp(p+1,r-1,0));
	return f[l][r][k]=res;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("fire.in","r",stdin);
	freopen("fire.out","w",stdout);
#endif
	read(n);
	memset(f,-1,sizeof(f));
	for(int i=1;i<=n;++i)
		read(a[i]);
	for(int i=1,j=1;i<=n;i=j+1)
	{
		while(j<n&&a[j+1]==a[i]) ++j;
		len[++m]=j-i+1;
		color[m]=a[i];
	}
	printf("%d\n",dp(1,n,0));
	return 0;
}