AtCoder Beginner Contest 406
AtCoder Beginner Contest 406
A - Not Acceptable
A点B分有个DDL,C点D分提交。问是否在DDL之前完成的提交。
先比较小时,如果小时一致再比较分钟。
#include<iostream>
#include<cstdio>
using namespace std;
int A,B,C,D;
int main()
{
cin>>A>>B>>C>>D;
if(C<A||(C==A&&D<B))
puts("Yes");
else puts("No");
return 0;
}
B - Product Calculator
有一个计算器一开始显示1
接下来执行了N个操作,在第\(i\)个操作,他会给当前显示的数乘上\(A_i\)
然而计算器最多显示K位,一旦结果超过了K位,就会变成\(1\)
问最终显示的数是多少。
首先数据范围用longlong可以存,但是不能直接做乘法。我们用sk表示k位9
我们实际要判断的是\(a\times b>sk\),那么处理一下可以变成\(a>sk/b\),这样可以很轻松的判断是否会乘爆了。
#include<iostream>
#include<cstdio>
using namespace std;
int N,K;
long long disp, maxk, A;
int n,k;
int main()
{
cin>>n>>k;
disp=1;
for(int i=1;i<=k;++i)maxk=maxk*10+9;
for(int i=1;i<=n;++i)
{
cin>>A;
if(maxk/disp>=A)disp*=A;
else disp=1;
}
cout<<disp<<endl;
return 0;
}
C - ~
称一个序列是波浪的,当且仅当:
- 长度至少为4
- \(A_1<A_2\)
- 存在一个\(2\le i<|A|\),满足\(A_{i-1}<A_i>A_{i+1}\)
- 存在一个\(2\le i<|A|\),满足\(A_{i-1}>A_i<A_{i+1}\)
给定一个\(1-n\)的排列,问有多少个连续的子段是波浪的。
因为只存在两个特殊的位置,意味着其他的都不符合大于两侧或者小于两侧,所以他们只能是单增或者单减
两个特殊位置则是两个特殊拐角,因此子段的形状只能是 单增-单减-单增 三段(其实题目名字已经告诉你了)
按照单增和单减合并段,枚举所有单减段然后考虑两侧的程度即可。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MAX = 300300;
int n,P[MAX];
long long ans=0;
vector<int> pos1,pos2;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&P[i]);
vector<pair<char,int> > v;
for(int i=1;i<n;++i)
if(P[i]<P[i+1])
{
if(v.empty() || v.back().first == '>')
v.push_back(make_pair('<',1));
else v.back().second++;
}
else
{
if(v.empty() || v.back().first == '<')
v.push_back(make_pair('>',1));
else v.back().second++;
}
int size=v.size();
for(int i=1;i<size-1;++i)
if(v[i].first=='>')
ans+=1ll*v[i-1].second*v[i+1].second;
printf("%lld\n",ans);
return 0;
}
D - Garbage Removal
有一个H行W列的网格,网格图中有\(N\)个垃圾,第\(i\)个位于\((X_i,Y_i)\)
现在有\(Q\)个询问,需要依次处理:
- Type1:给出
1 x的形式,回答第\(x\)行的垃圾数目,然后将他们移除- Type2:给出
2 y的形式,回答第\(y\)列的垃圾数目,然后将他们移除
我们只需要分行/列维护每个行/列中所有的垃圾以及他们的位置
每次删除的时候首先输出这一行/列的大小,然后在对应的行和对应的列把它们删除即可
我们需要支持一个如下操作的数据结构:
- 插入元素
- 获取元素个数
- 支持快速删除特定元素
使用STL中的set即可实现。
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
const int MAX = 200200;
int n,Q,H,W;
set<int> X[MAX],Y[MAX];
int main()
{
scanf("%d%d%d",&H,&W,&n);
for(int i=1;i<=n;++i)
{
int x,y;
scanf("%d%d",&x,&y);
X[x].insert(y);
Y[y].insert(x);
}
scanf("%d",&Q);
while(Q--)
{
int t,x;
scanf("%d%d",&t,&x);
if(t==1)
{
printf("%d\n",X[x].size());
for(auto a:X[x])Y[a].erase(x);
X[x].clear();
}
else
{
printf("%d\n",Y[x].size());
for(auto a:Y[x])X[a].erase(x);
Y[x].clear();
}
}
return 0;
}
E - Popcount Sum 3
给定\(N\)和\(K\),找到所有不超过\(N\)的数中,二进制下恰好有\(K\)个\(1\)的所有数之和,输出模\(9982244353\)的结果
很明显的数位\(dp\)
设\(f[i][j][0/1]\)表示当前考虑到了\(2^i\)位,已经有\(j\)位是\(1\),是否已经严格比\(N\)小,的数的个数
设\(g[i][j][0/1]\)表示当前考虑到了\(2^i\)位,已经有\(j\)位是\(1\),是否已经严格比\(N\)小,的数的已确定的数位的和
每次枚举这一位填\(0\)还是填\(1\),进行转移即可。
转移参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
const int MOD=998244353;
ll N,K;
int f[100][100][2];
int g[100][100][2];
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
cin>>N>>K;
f[60][0][0]=1;
for(int i=59;i>=0;--i)
{
if(N&(1ll<<i))
for(int j=0;j<=K;++j)
{
add(f[i][j][1],f[i+1][j][0]);
add(g[i][j][1],g[i+1][j][0]);
add(f[i][j][1],f[i+1][j][1]);
add(g[i][j][1],g[i+1][j][1]);
add(f[i][j+1][0],f[i+1][j][0]);
add(g[i][j+1][0],g[i+1][j][0]);
add(g[i][j+1][0],(1ll<<i)*f[i+1][j][0]%MOD);
add(f[i][j+1][1],f[i+1][j][1]);
add(g[i][j+1][1],g[i+1][j][1]);
add(g[i][j+1][1],(1ll<<i)*f[i+1][j][1]%MOD);
}
else
for(int j=0;j<=K;++j)
{
add(f[i][j][0],f[i+1][j][0]);
add(g[i][j][0],g[i+1][j][0]);
add(f[i][j][1],f[i+1][j][1]);
add(g[i][j][1],g[i+1][j][1]);
add(f[i][j+1][1],f[i+1][j][1]);
add(g[i][j+1][1],g[i+1][j][1]);
add(g[i][j+1][1],(1ll<<i)*f[i+1][j][1]%MOD);
}
}
int ans=(g[0][K][0]+g[0][K][1])%MOD;
cout<<ans<<endl;
}
return 0;
}
F - Compare Tree Weights
有一棵树,有\(N\)个节点,每个节点有一个重量,初始每个节点的重量都是\(1\)
给出\(Q\)个询问,依次处理:
1 x w:给节点\(x\)的重量加上\(w\)2 y:把第\(y\)条边断开,树会变成两部分,回答这两部分的重量和的差
以任意一个点作为根节点,断开一条边,其中一部分一定是一棵完整的子树,而完整子树在dfs序上是连续的
那么我们只需要知道所有节点的重量和,以及快速求一段dfs序上的重量和即可。
所有节点的重量和全局维护一下就行了。
快速求一段区间和用树状数组可以很容易的解决。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
const int MAX=300300;
vector<int> E[MAX];
int x[MAX],y[MAX];
int dfn[MAX],low[MAX],f[MAX],cnt;
int n;
ll t[MAX],totw;
int lowbit(int x){return x&(-x);}
void add(int x,int y){while(x<=n)t[x]+=y,x+=lowbit(x);}
ll sum(int x){ll s=0;while(x>0)s+=t[x],x-=lowbit(x);return s;}
void dfs(int u,int fa)
{
f[u]=fa;dfn[u]=low[u]=++cnt;
for(auto v:E[u])
{
if(v==fa)continue;
dfs(v,u);
low[u]=max(low[u],low[v]);
}
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
x[i]=read();
y[i]=read();
E[x[i]].push_back(y[i]);
E[y[i]].push_back(x[i]);
}
dfs(1,0);
for(int i=1;i<=n;++i)add(dfn[i],1);
totw=n;
int Q=read();
while(Q--)
{
int op=read();
if(op==1)
{
int x=read(),w=read();
totw+=w;
add(dfn[x],w);
}
else
{
int i=read();
if(f[x[i]]!=y[i])swap(x[i],y[i]);
int l=dfn[x[i]],r=low[x[i]];
ll sub=sum(r)-sum(l-1);
ll ans=abs(totw-sub-sub);
printf("%lld\n",ans);
}
}
return 0;
}
G - Travelling Salesman Problem
有\(N\)个商人在坐标轴上,第\(i\)个商人位于\(X_i\),销售物品\(i\)。
一开始你在位置\(0\)上,需要依次购买物品\(1,2,...,N\)
有三种操作:
- 你自己移动一个单位,花费\(C\)
- 让一个商人移动一个单位,花费\(D\)
- 如果你和一个商人位于坐标轴上同一个位置,那么可以从他手中购买物品,花费\(0\)
回答最小花费,并输出一个位置序列,第\(i\)个数表示你购买物品\(i\)时所在的位置。
考虑一个暴力\(dp\),设\(f[i][j]\)表示当前已经购买了\(i\)个物品,购买完之后你在的位置为\(j\),
显而易见的,在购买第\(i-1\)个物品之前,你不需要让第\(i\)个商人移动,所以每次你在考虑购买第\(i\)个物品时,只需要考虑你自己和第\(i\)个商人的位置。
因此初值\(f[0][0]=0\)
\(f[i][j]=\min_k\{f[i-1][k]+abs(k-j)*C+abs(X_i-j)*D\}\)
假设函数\(f_i(j)=f[i][j]\),设\(g_i(j)=\min\{f_{i}(k)+C|k-j|\}\)
于是\(f_{i+1}(j)=g_i(j)+D|X_i-j|\)
注意到\(f\)和\(g\)的形式都是类似于每次增加上一个线段。
而线段的端点数量是\(O(n)\)级别的,因此我们可以用map暴力维护这些线段变化的位置。当然,也可以使用线段树来进行维护。
假设我们已经求出了\(f_i\),那么\(f_i\)一定可以写成左端点值+若干条线段斜率的形式。
考虑\(g_i\):
对于靠数轴负半轴的部分:
如果\(f_i(x)\)和\(f_i(x-1)\)之间的斜率的绝对值超过了\(C\),那么显然他们不如向中心移动一位(因为向中心移动一位的斜率只有\(C\),相比之下是更优的)
直到某个位置斜率绝对值小于\(C\),此时之前的位置显然都用当前的位置去进行更新,而之后的位置由于\(f\)的斜率都小于\(C\),因此想其他方向移动不如由自己的\(f\)值更新过来。
对于靠数轴正半轴的部分是类似的。
这样子我们可以通过\(f\)直接更新出\(g\)的值。而在此基础上加上两条线段即可得到新一轮\(f\)的值。
重复此过程,我们就可以得到最终的\(dp\)值。

浙公网安备 33010602011771号