AtCoder Beginner Contest 271
尽量写的高质量一点,只写有意义的题目。
C
可以像题解一样 通过二分来解决本题,这里提供一个 桶+双指针 的解法。
先将书的序号排序,将相同的放在最后(unique 函数),用桶维护共有哪些书,两个指针分别维护从前面读了几本书 和 从后面换了几本书,每次模拟换书的过程需要更新桶数组。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,a[N],tong[N];
signed main()
{
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
sort(a+1,a+n+1);unique(a+1,a+n+1);
for(int i=1;i<=n;++i)if(a[i]<=n)tong[a[i]]++;
int i,hav=0,lst=n;
for(i=1;i<=n;++i)
{
if(tong[i]){hav++;continue;}
if(hav>lst-2)break;
if(a[lst]<=n)tong[a[lst]]--;
if(a[lst-1]<=n)tong[a[lst-1]]--;
lst-=2;
}
cout<<i-1;
return 0;
}
D
考虑 dp,设 \(dp[i][j]\) 表示前 \(i\) 个数总和为 \(j\) 是否有方案。
转移:\(dp[i][j]\mid=dp[i-1][j-a[i]],dp[i][j]\mid=dp[i-1][j-b[i]]\)
考虑如何输出方案,我们可以倒序模拟 dp 的过程,每次看 \(dp[i-1][j-a[i]]\) 和 \(dp[i-1][j-b[i]]\) 是否为 \(1\),用栈记录每次的选择,最后输出即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n,k,a[105],b[105];
bool dp[105][10005];
signed main()
{
cin>>n>>k;
for(int i=1;i<=n;++i)
cin>>a[i]>>b[i];
dp[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=k;++j)
{
if(j-a[i]>=0)dp[i][j]|=dp[i-1][j-a[i]];
if(j-b[i]>=0)dp[i][j]|=dp[i-1][j-b[i]];
}
if(dp[n][k])cout<<"Yes\n";
else cout<<"No\n";
stack<char>s;
if(dp[n][k])
{
int nown=n,nowk=k;
while(nown)
{
if(a[nown]<=nowk&&dp[nown-1][nowk-a[nown]])
{
s.push('H');
nowk-=a[nown];
nown--;
}
else if(b[nown]<=nowk&&dp[nown-1][nowk-b[nown]])
{
s.push('T');
nowk-=b[nown];
nown--;
}
}
}
for(;!s.empty();s.pop())putchar(s.top());
return 0;
}
E
容易想到 dp。
考虑朴素的 dp,设 \(dp[i][j]\) 表示当前在 \(i\) 点,当前用的边的序列是 \(E[1]-E[j]\) 的子序列的最小花费。
转移:\(dp[v][j]=\min(dp[v][j-1],dp[u][j-1]+len(u,v)\mid(u,v)==E[j])\)
滚动数组一下:\(dp[v]=\min(dp[v],dp[u]+len(u,v)\mid(u,v)\in E)\)
观察发现第二种决策可行 当且仅当 \((u,v)\) 这条边在 \(E\) 中,所以只更新 \(E\) 中的 \(k\) 条边即可。
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m,k,u[N],v[N],w[N],f[N];
signed main()
{
memset(f,0x3f,sizeof(f));
cin>>n>>m>>k;
for(int i=1;i<=m;++i)cin>>u[i]>>v[i]>>w[i];
f[1]=0;
while(k--)
{
int x;cin>>x;
f[v[x]]=min(f[v[x]],f[u[x]]+w[x]);
}
if(f[n]==4557430888798830399)cout<<-1;
else cout<<f[n];
return 0;
}
F
发现 \((1,1)\) 到 \((n,n)\) 的路径长为 \(2n-2\),直接搜索复杂度为 \(O(2^{2n-2})\),会超时,考虑折半搜索。
从 \((1,1)\) 和 \((n,n)\) 分别开始搜索,搜 \(n-1\) 步,用前缀和记录走到每个汇合点不同答案的方案数,再把两边拼起来即可。时间复杂度为 \(O((n-1)2^{n-1})\)。
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,a[30][30];
signed main()
{
cin>>n;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
cin>>a[i][j];
map<int,int>qz[30][30],qz2[30][30];
for(int i=0;i<(1<<(n-1));++i)
{
int x=1,y=1,ans=0;
for(int s=0;s<=n-2;++s)
{
ans^=a[x][y];
if(i&(1<<s))x++;
else y++;
}
qz[x][y][ans]++;
}
for(int i=0;i<(1<<(n-1));++i)
{
int x=n,y=n,ans=0;
for(int s=0;s<=n-2;++s)
{
if(i&(1<<s))x--;
else y--;
ans^=a[x][y];
}
qz2[x][y][ans^a[n][n]]++;
}
int cnt=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
map<int,int>::iterator from=qz[i][j].begin();
map<int,int>::iterator to=qz[i][j].end();
while(from!=to)
{
int key=from->first,val=from->second;
cnt+=val*qz2[i][j][key];from++;
}
}
cout<<cnt;
return 0;
}