前言
孩子们,明天新生赛,会赢吗qwq
一、A. Sleeping Through Classes
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void solve()
{
int n,k;
cin>>n>>k;
string s;
cin>>s;
s=" "+s;
int i=1;
while(i<=n&&s[i]=='0')
{
i++;
}
int ans=i-1;
while(i<=n)
{
int l=k;
while(i<=n&&l>0)
{
if(s[i]=='1')
{
l=k;
}
else
{
l--;
}
i++;
}
int j=i;
while(j<=n&&s[j]=='0')
{
j++;
}
ans+=j-i;
i=j;
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
上来这个A就不简单,想了一会才过的......
题意倒是很明确,直接模拟就行,就是碰到必须要上的课上完刷新清醒时间,否则的话就可以统计答案了。
二、B. Niko's Tactical Cards
妈的,我感觉这个B难爆了,好不容易才想出来,到底为什么会有那么多人过B......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
ll cmx=0;
ll cmn=0;
for(int i=1;i<=n;i++)
{
ll mx=max({cmx-a[i],b[i]-cmn,b[i]-cmx,cmn-a[i]});
ll mn=min({cmx-a[i],b[i]-cmn,b[i]-cmx,cmn-a[i]});
cmx=mx;
cmn=mn;
}
cout<<cmx<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题真的难想,只能说B题放dp太搞了......
因为操作1可以保持当前的分数不变,而操作2可以让当前的分数取相反数。那么操作1肯定是希望当前的分数越大越好,而操作2肯定是希望让当前分数越小越好。那么操作1肯定是在之前能达到的最大值的基础上操作,操作2肯定是在之前能达到的最小值的基础上操作。所以可以考虑分别维护之前能达到的最大值和最小值,然后看这两个值排列组合能否更新最大最小值即可。
三、C. Kanade's Perfect Multiples
吃一堑吃一堑吃一堑了......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
//赛时傻逼代码,但能过
void solve1()
{
ll n,k;
cin>>n>>k;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
//一定要排序!!!!!
sort(a.begin()+1,a.end());
map<ll,vector<int>>mp;
for(int i=1;i<=n;i++)
{
mp[a[i]].push_back(i);
}
vector<int>vis(n+1);
vector<ll>p;
for(auto [x,vec]:mp)
{
int first=vec[0];
if(!vis[first])
{
p.push_back(x);
for(auto i:vec)
{
vis[i]=1;
}
for(ll c=2;c<=n-first+3&&x*c<=k;c++)
{
auto iter=mp.find(x*c);
if(iter!=mp.end())
{
for(auto i:iter->second)
{
vis[i]=1;
}
}
else
{
cout<<-1<<endl;
return ;
}
}
}
}
cout<<p.size()<<endl;
for(auto x:p)
{
cout<<x<<" ";
}
cout<<endl;
}
//正解
void solve2()
{
ll n,k;
cin>>n>>k;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
map<ll,vector<int>>mp;
for(int i=1;i<=n;i++)
{
mp[a[i]].push_back(i);
}
vector<int>vis(n+1);
vector<ll>p;
for(auto [x,vec]:mp)
{
int first=vec[0];
if(!vis[first])
{
p.push_back(x);
for(auto i:vec)
{
vis[i]=1;
}
for(ll j=2*x;j<=k;j+=x)
{
auto iter=mp.find(j);
if(iter!=mp.end())
{
for(auto i:iter->second)
{
vis[i]=1;
}
}
else
{
cout<<-1<<endl;
return ;
}
}
}
}
cout<<p.size()<<endl;
for(auto x:p)
{
cout<<x<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve2();
}
return 0;
}
其实赛时已经考虑到了势能分析,但由于是记录每个数的下标是否访问过,且往后枚举的步长和当前所在的位置有关,所以必须进行排序,而赛时恰好就是一直没想到需要排序......
整体思路就是对于原数组中出现的数,肯定是选择最小的因数放入b数组。举个例子,对于[2,3,4,6,8]这个数组,肯定是把2放进b数组,这样后面的[4,6,8]就都被覆盖了。
先说说赛时的傻逼思路,在排完序后,用map维护每个数出现的位置。之后从小到大枚举每个出现的数,若当前数没在之前的过程中被标记,那么就说明这个数必须要放入b数组,那么就标记这个数所有出现的位置,然后去枚举这个数的倍数。在枚举这个数的倍数的过程中,在保证不超过k的前提下,若当前数出现在i位置,那么只需要枚举到n-i+3倍,确保有没出现的会被抓出来即可。因为后面的倍数要么超过k,要么因为数组长度必然不会出现。
正解的势能分析就是,对于每个选择的数,直接暴力枚举其倍数。因为数组的元素个数是有限的,所以这个数再大也不会大到哪去。即使存在一个非常大的数,由于是从小到大枚举倍数,所以在中间必然会找到一个没出现的倍数,就直接return了,所以可以直接暴力枚举。
四、D. Taiga's Carry Chains
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int MAXB=64;
const int MAXK=32;
int dp[MAXB+1][MAXK+1][2];
ll n,k;
void clear()
{
for(int i=0;i<=MAXB;i++)
{
for(int j=0;j<=k;j++)
{
dp[i][j][0]=dp[i][j][1]=INF;
}
}
}
void solve()
{
cin>>n>>k;
//当k>=32时,在每一步都使得n产生一个进位后,n会变成2的幂
//之后每次增加都会产生一个进位,所以答案就是popcount(n)+k-1
int pc=__builtin_popcount(n);
if(n==0)
{
cout<<max(0ll,k-1)<<endl;
return ;
}
if(k>=32)
{
cout<<pc+k-1<<endl;
return ;
}
//观察到,最终的答案为popcount(n)+k-popcount(end),end为最终的结果
//所以为了最大化这个值,需要最小化popcount(end)
//考虑定义dp[i][j][c]为在处理了i个最小有效位后,用了j次操作
//c=0表示没有来自i-1的进位,c=1表示有进位,此时的最小值
//所以对于第i位,要么选择这位操作要么不操作
clear();
dp[0][0][0]=0;
for(int i=0;i<MAXB;i++)
{
int cur=(n>>i)&1;
for(int j=0;j<=k;j++)
{
for(int c=0;c<=1;c++)
{
if(dp[i][j][c]==INF)
{
continue;
}
//当前位加上之前的总和
int sum=cur+c;
//当前位的状态
int bit=sum&1;
//对下一位的进位
int nc=sum>>1;
dp[i+1][j][nc]=min(dp[i+1][j][nc],dp[i][j][c]+bit);
if(j+1<=k)
{
//操作后当前位加一
sum++;
bit=sum&1;
nc=sum>>1;
dp[i+1][j+1][nc]=min(dp[i+1][j+1][nc],dp[i][j][c]+bit);
}
}
}
}
int ans=INF;
for(int j=0;j<=k;j++)
{
for(int c=0;c<=1;c++)
{
if(dp[MAXB][j][c]==INF)
{
continue;
}
ans=min(ans,dp[MAXB][j][c]+c);
}
}
cout<<pc+k-ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
逆天dp思路,补题的时候全往贪心上想了,压根没想到应该dp......
首先上来这个就很难想到,就是当k大于等于32时,因为n是2的30次方,所以此时肯定把原来位置的1全进位完了,剩下的就是2的某次方的形式,那么往后每一次操作都可以产生一个进位。那么多举几个例子就能发现,最终的答案为popcount(n)+k-1。推广一下有,当k小于32时,最终产生的进位数为popcount(n)+k-popcount(end),这里end是最终的数字。那么为了最大化这个值,可以考虑最小化popcount(end)。
之后就考虑定义dp[i][j][c]为在处理了i个有效位后,用了j次操作,c=0表示没有来自低位的进位,c=1表示有来自低位的进位,此时的最小值。所以对于第i位,可以选择操作或者不操作。所以每次计算出当前位加上之前的进位的总和,当前位的状态以及对下一位的进位。那么不操作就是直接加上当前位的状态,操作就是让总和加1,再加上此时当前位的状态。最后,枚举所有可能的状态,求出最小值,注意要考虑进位。
总结
还得练,最近几周感觉多训构造挺有效的,思路明显清晰了很多。