(注:上图真实,未F12)
第一次AK ABC,写个题解纪念一下吧
AtCoder比赛页面传送门
A - We Love Golf
AtCoder题目页面传送门
给定(a,b,c),问区间([b,c])内是否存在整数(x)使得(amid x)。
(cin[1,1000],1leq aleq bleq1000)。
没什么可说的,直接跑一遍(bsim c)判断一下即可。
代码(现场代码,下同):
#include<bits/stdc++.h>
using namespace std;
int main(){
int a,b,c;
cin>>a>>b>>c;
for(int i=b;i<=c;i++)if(i%a==0)return puts("OK"),0;
puts("NG");
return 0;
}
B - 1%
AtCoder题目页面传送门
原有(100)円,每年增加(1\%),不足(1)円的零头被抛弃。问多少年后能(geq n)円。
(ninleft[101,10^{18} ight])。
直接暴力,一开始(100),每次( imes 1.01)并下取整直到(geq n),顺便计个次。
观察样例可发现,最多增加(3760)次,不用担心TLE。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
long long n;
cin>>n;
long long now=100;
for(int i=1;;i++){
now=1.01*now;
if(now>=n)return cout<<i,0;
}
return 0;
}
C - Many Requirements
洛谷题目页面传送门 & AtCoder题目页面传送门
给定(n,m,s)和(s)个四元组,第(i)个为((a_i,b_i,c_i,d_i)),求在所有满足(1leq lis_1leq lis_2leqcdotsleq lis_nleq m)的长度为(n)的整数序列(lis)中,最大的(sumlimits_{i=1}^s[lis_{b_i}-lis_{a_i}=c_i]d_i)。
(nin[2,10],min[1,10],sin[1,50])。
考虑枚举所有的(lis)算答案并取最大值。看起来满足条件的(lis)的数量大概是(mathrm O(m^n))级别的,但其实绝大多数每个数(in[1,m])、长度为(n)的整数序列都不满足条件。考虑算出满足条件的(lis)的数量最大是多少。
显然,当(n,m)都最大时,数量最大。于是我们写这样一个程序跑一跑:
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l) for(int i=l;i<=10;i++)
int main(){
int ans=0;
rep(a,1)rep(b,a)rep(c,b)rep(d,c)rep(e,d)rep(f,e)rep(g,f)rep(h,g)rep(i,h)rep(j,i)ans++;
cout<<ans;
return 0;
}
跑出来当(n=m=10)时数量只有(92378)。这时候就可以放心大胆地暴搜了,对于一个(lis)计算答案的时间复杂度为(mathrm O(s))。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10,S=50;
int n,m,s;
int a[S+1],b[S+1],c[S+1],d[S+1];
int lis[N+1];
int ans;
void dfs(int x=1,int now=1){
if(x==n+1){
int res=0;
for(int i=1;i<=s;i++)if(lis[b[i]]-lis[a[i]]==c[i])res+=d[i];
ans=max(ans,res);
return;
}
lis[x]=now;
for(int i=now;i<=m;i++)dfs(x+1,i);
}
int main(){
cin>>n>>m>>s;
for(int i=1;i<=s;i++)cin>>a[i]>>b[i]>>c[i]>>d[i];
dfs();
cout<<ans;
return 0;
}
D - Floor Function
洛谷题目页面传送门 & AtCoder题目页面传送门
给定(a,b,cinmathbb Z),求(maxlimits_{i=1}^cleft{leftlfloordfrac{ai}b ight floor-aleftlfloordfrac ib ight floor ight})。
(ainleft[1,10^6 ight],b,cinleft[1,10^{12} ight])。
直接枚举(i)是(mathrm O(c))的,显然过不去。考虑对这个(leftlfloordfrac{ai}b ight floor-aleftlfloordfrac ib ight floor)式子变变形。看上去没有思路,考虑将下取整换为原数减去小数部分的形式:
此时显然函数值只与(imod b)有关,即(f(i)=f(imod b))。不妨只考虑所有的(i<b)。此时显然(left{dfrac ib ight}=dfrac ib)。所以
显然当(i=min(b-1,c))时,(f(i))最大。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
long long a,b,c;
cin>>a>>b>>c;
if(b-1>c)cout<<int(a*(1.*c/b));
else cout<<int(a*(1.*(b-1)/b));
return 0;
}
E - Rotation Matching
这其实是最难的一题,因为F是套路题。
洛谷题目页面传送门 & AtCoder题目页面传送门
有(n)个人,共(n)轮比赛,每轮所有人往右移一格,最后面的人移到最前面。每轮有(m(2m+1leq n))场比赛,每场比赛描述为两个位置,表示每轮都是固定这两个位置的人打比赛,比赛的人集不相交。给出一组(m)场比赛的描述,使得每人在(n)轮里打的(2m)场比赛的对手两两不同。
(ninleft[1,10^5 ight])。
一个显然的结论是:若一个人满足题意,则所有人满足题意,因为所有人是轮换对称的(是这么说的吧?)。
当(n)是奇数时,方法显然:位置(i)跟(n-i+1)配。
当(n)是偶数时呢?naive地想,跟(n)是奇数的方法一样的话,那位置关于中心对称的两个人会交两次手,不行。不难发现,根据(2m+1leq n)可以得出至少有(2)个位置没有比赛,暗示了我们可以牺牲空间换取奇偶性。然后这也没啥可说的,只能手玩一会儿,可以玩出来一种方法:从中间截半,如果两半大小是奇数,就把左一半的最右边分到右一半去。此时两半大小都是偶数,左边那半按naive方法,右边那半牺牲掉最后一个,剩下来奇数个,用奇数的方法。这样是正确的,别问我咋想到的。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int main(){
cin>>n>>m;
if(n&1)//n为奇数
for(int i=1;i<=m;i++)cout<<i<<" "<<n-i+1<<"
";
else{//n为偶数
for(int i=1;i<=n/4&&m;i++,m--)cout<<i<<" "<<n/4*2-i+1<<"
";//左一半
for(int i=n/4*2+1;m;i++,m--)cout<<i<<" "<<n/4*2+1+n-1-i<<"
";//右一半
}
return 0;
}
F - LIS on Tree
洛谷题目页面传送门 & AtCoder题目页面传送门
给定一棵大小为(n)的树,求对于每个点,(1)到这个点组成的序列的LIS。
(ninleft[1,2 imes10^5 ight])。
(这是我第一个做出来的题)
考虑跟普通的线性结构上的LIS设类似的状态,状态转移方程也类似。
普通的线性LIS可以用BIT优化,而这里树上DFS回溯的时候需要撤销,我们可以对于每个离散化后的值开一个multiset
来撤销,每次将multiset
中最大的数扔到BIT里。可是,这样有可能变小,BIT是处理不了的,于是可以用线段树维护。
代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=200000;
int n;
int a[N+1];
vector<int> nums;
void discrete(){//离散化
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=n;i++)a[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin();
}
vector<int> nei[N+1];
multiset<int> st[N];
struct segtree{//线段树
struct node{int l,r,mx;}nd[N<<2];
#define l(p) nd[p].l
#define r(p) nd[p].r
#define mx(p) nd[p].mx
void bld(int l=0,int r=nums.size()-1,int p=1){
l(p)=l;r(p)=r;mx(p)=0;
if(l==r)return;
int mid=l+r>>1;
bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
}
void init(){bld();}
void sprup(int p){mx(p)=max(mx(p<<1),mx(p<<1|1));}
void chg(int x,int v,int p=1){//单点修改
if(l(p)==r(p))return mx(p)=v,void();
int mid=l(p)+r(p)>>1;
chg(x,v,p<<1|(x>mid));
sprup(p);
}
int _mx(int l,int r,int p=1){//区间最大值
if(l>r)return 0;
if(l<=l(p)&&r>=r(p))return mx(p);
int mid=l(p)+r(p)>>1,res=0;
if(l<=mid)res=max(res,_mx(l,r,p<<1));
if(r>mid)res=max(res,_mx(l,r,p<<1|1));
return res;
}
}segt;
int dp[N+1],ans[N+1];
void dfs(int x=1,int fa=0){
dp[x]=segt._mx(0,a[x]-1)+1;//转移方程
ans[x]=max(ans[fa],dp[x]);
st[a[x]].insert(dp[x]);
segt.chg(a[x],*--st[a[x]].end());
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa)continue;
dfs(y,x);
}
st[a[x]].erase(st[a[x]].find(dp[x]));//回溯时撤销
segt.chg(a[x],*--st[a[x]].end());//撤销
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
discrete();
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
nei[x].pb(y);nei[y].pb(x);
}
for(int i=0;i<nums.size();i++)st[i].insert(0);
segt.init();
dfs();
for(int i=1;i<=n;i++)cout<<ans[i]<<"
";
return 0;
}