Atcoder Beginner Contest 315 解题报告
Atcoder Beginner Contest 315
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\) 号点的距离。
那么状态转移方程也很容易写了:
意思就是,枚举上一个经过的点 \(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\),此时方程变为:
用 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
还没做出来,咕。