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

posted @ 2022-10-02 12:22  lnwhl  阅读(46)  评论(0编辑  收藏  举报