Atcoder Beginner Contest 315 解题报告

Atcoder Beginner Contest 315

Contest link

Hints

你们最想要的虽然只有一点

D 尝试优化暴力。
F
F1 DP?
F2 根据罚时的特性,优化一下?
G $n\le10^6$?

A - tcdr

没啥好说的,模拟。

代码实现

void Solve()
{
	while(char ch=getchar())
	{
		if(!isalpha(ch))return;
		if(ch!='a'&&ch!='e'&&ch!='i'&&ch!='o'&&ch!='u')putchar(ch);
	}
}

B - The Middle Day

没啥好说的,统计总天数,找到中间的一天,再枚举月即可。

代码实现

const int N=105;
int n,a[N];
void Solve()
{
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
	sum=sum+1>>1;
	for(int i=1;i<=n;i++)
		if(sum>a[i])sum-=a[i];
		else return cout<<i<<" "<<sum,void();
}

C - Flavors

只有两种情况:

  • 选择美味值最高的和第二高的;
  • 选择美味值最高的,还有与美味值最高的口味不同的中的美味值最高的。

代码实现

const int N=300005;
int n;
struct ice
{
	int f,s;
	bool operator<(const ice B)const{return s>B.s;}
}a[N];
void Solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i].f>>a[i].s;
	sort(a+1,a+n+1);
	int x=0,y=0;
	if(a[1].f==a[2].f)x=a[2].s;
	for(int i=2;i<=n;i++)
		if(a[1].f!=a[i].f){y=a[i].s;break;}
	cout<<a[1].s+max(x>>1,y);
}

D - Magical Cookies

吐槽比 E 毒瘤。

有一个暴力的想法:每次检查每一行每一列,如果全一样且大于一个就标记上,然后全部删掉。

这样做,最坏情况是每次删除了一行/一列,但是却扫过了整个矩阵,时间复杂度 \(\Theta(nm(n+m))\),成功 TLE。

我们发现,这个做法的瓶颈在于检查哪一行/列可以被删除。

怎样优化?

我们可以记录每一行/列所有 \(26\) 个字母的出现次数以及剩下的字母个数。这样做,我们就可以在 \(\Theta((n+m)|\Sigma|)\) 的时间找出哪一行/列可以被删除。

进一步的,我们可以直接记录每一行/列出现过的字母个数,做到 \(\Theta(n+m)\) 的 check。

然后每次用 vector 记录要删除的点,删除的时候即可更新一下行列信息即可。注意避免重复删点。

最坏情况下,要删除 \(nm\) 个点,删点的复杂度 \(\Theta(1)\);要 check \(n+m\) 次,每次 check 是 \(\Theta(n+m)\) 的。总复杂度 \(\Theta((n+m)^2)\)

代码实现

const int N=2005;
int n,m;
string g[N];
int cntr[N][26],cntc[N][26],typr[N],typc[N],remr[N],remc[N];
void add(int x,int y)
{
	int t=g[x][y]-'a';
	if(!cntr[x][t]++)typr[x]++;
	if(!cntc[y][t]++)typc[y]++;
	remr[x]++,remc[y]++;
}
void del(int x,int y)
{
	if(g[x][y]==' ')return;
	int t=g[x][y]-'a';
	g[x][y]=' ';
	if(!--cntr[x][t])typr[x]--;
	if(!--cntc[y][t])typc[y]--;
	remr[x]--,remc[y]--;
}
vector<PII>mk;
void Solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>g[i],g[i]="$"+g[i];
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)add(i,j);
	while(1)
	{
		bool ok=0;
		for(int i=1;i<=n;i++)
			if(typr[i]==1&&remr[i]>1)
			{
				ok=1;
				for(int j=1;j<=m;j++)mk.PB(i,j);
			}
		for(int i=1;i<=m;i++)
			if(typc[i]==1&&remc[i]>1)
			{
				ok=1;
				for(int j=1;j<=n;j++)mk.PB(j,i);
			}
		if(!ok)break;
		for(PII i:mk)del(i.first,i.second);
		mk.clear();
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)ans+=(bool)isalpha(g[i][j]);
	cout<<ans;
}

E - Prerequisites

水题。

把依赖关系看成一张图,则它是个 DAG。

直接从 \(1\) 开始 dfs,求出 dfs 树的后序遍历即可。注意别把 \(1\) 输出了。

时间复杂度 \(\Theta(n)\)

代码实现

const int N=200005;
int n;
VI g[N];
bool vis[N];
void dfs(int x)
{
	if(vis[x])return;
	vis[x]=1;
	for(int y:g[x])dfs(y);
	cout<<x<<" ";
}
void Solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int sz;cin>>sz;
		while(sz--){int x;cin>>x;g[i].PB(x);}
	}
	for(int i:g[1])dfs(i);
}

F - Shortcuts

DP。令 \(dp_{i,j}\) 表示走到第 \(i\) 个点,跳过了 \(j\) 个点时,时间的最小值。(罚时先不用管,可以最后统一再算。)

再令 \(dist(i,j)\) 表示 \(i\) 号点与 \(j\) 号点的距离。

那么状态转移方程也很容易写了:

\[dp_{1,0}=0,dp_{i,j}=\min_{k=\max\{1,i-j-1\}}^{i-1}\{dp_{k,j-(i-1-k)}+dist(k,i)\}\ (0\le j<i,i\ge2). \]

意思就是,枚举上一个经过的点 \(k\)

最后加上罚时取最小值即可。

但是,这里有 \(\Theta(n^2)\) 个状态,转移还要 \(\Theta(n)\),但是数据范围显然承受不了 \(\Theta(n^3)\) 的复杂度。

注意到罚时是 \(2^{C-1}\),而坐标的范围只有 \(10^4\),即任意两个点的距离最多是 \(10^4\sqrt2\),从头走到尾最多花 \(10^8\sqrt2\) 秒。但是如果 \(C>30\),仅罚时就已经有 \(2^{30}\approx10^9\) 秒,显然是不值的。

所以我们只需要转移 \(j<S=30\) 的状态就可以了。

时间复杂度 \(\Theta(nS^2)\)

代码实现

const int N=10005,S=30;
int n,x[N],y[N];
double dp[N][S];
double dis(int i,int j)
{
	return hypot(x[i]-x[j],y[i]-y[j]);
}
void Solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>x[i]>>y[i];
	memset(dp,99,sizeof(dp));
	dp[1][0]=0;
	for(int i=2;i<=n;i++)
		for(int j=0;j<S;j++)
			for(int k=i-1;k>=max(1,i-j-1);k--)
				dp[i][j]=min(dp[i][j],dp[k][j-(i-1-k)]+dis(k,i));
	double p=1.0;
	for(int i=1;i<S;i++)dp[n][i]+=p,p*=2.0;
	double ans=1e100;
	for(int i=0;i<S;i++)ans=min(ans,dp[n][i]);
	Fix(20);cout<<ans;
}

G - \(Ai + Bj + Ck = X (1 \le i, j, k \le N)\)

exgcd 板子题。

看到 \(n\le10^6\),我们可以枚举 \(k\),此时方程变为:

\[Ai+Bj=(X-Ck) \]

用 exgcd 解出此方程的解数量即可(别忘了 \(1\le i,j\le n\))。

代码很烦。时间复杂度 \(\Theta(n\log\max(A,B))\)

中间结果可能爆 long long,可能需要 __int128

代码实现

//#define lll __int128
ll n,ans;
ll divup(lll,ll);
ll divdown(lll a,ll b)
{
	if(a>=0)return a/b;
	return -divup(-a,b);
}
ll divup(lll a,ll b)
{
	if(a>=0)return (a+b-1)/b;
	return -divdown(-a,b);
}
void exgcd(ll a,ll b,lll &x,lll &y)
{
	if(!b)return x=1,y=0,void();
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
ll count(ll a,ll b,ll c)
{
	if(c<=0)return 0;
	ll g=__gcd(a,b);
	if(c%g)return 0;
	a/=g,b/=g,c/=g;
	lll x,y;
	exgcd(a,b,x,y);
	x*=c,y*=c;
	ll l1,r1,l2,r2;
	l1=divup(-x+1,b);
	r1=divdown(n-x,b);
	l2=divup(y-n,a);
	r2=divdown(y-1,a);
	return max(0ll,min(r1,r2)-max(l1,l2)+1);
}
void Solve()
{
	ll a,b,c,x;
	cin>>n>>a>>b>>c>>x;
	for(ll i=1;i<=n;i++)
		ans+=count(a,b,x-i*c);
	cout<<ans;
}

Ex - Typical Convolution Problem

还没做出来,咕。

posted @ 2023-08-20 10:55  No_Play_Yes_Splay  阅读(115)  评论(0编辑  收藏  举报