喵哈哈村的魔法考试 Round #1 (Div.2) 题解
喵哈哈村的魔法考试 Round #1 (Div.2) 题解
特别感谢出题人,qscqesze。
也特别感谢测题人Xiper和CS_LYJ1997。
没有他们的付出,就不会有这场比赛。
A 喵哈哈村的魔法石
暴力(qscqesze):观察数据范围,显然最多10000个A,10000个B,所以最暴力的做法就是两个for循环,一个forA,一个forB,复杂度就是O(10000*10000),这个出题人因为是div2 A,所以就直接放过了的。
机智的做法(CS_LYJ1997):在暴力的基础上优化,第二维我们根本不需要枚举。我们直接O(1)判断是否成立就好了嘛。
#include<bits/stdc++.h>
using namespace std;
int main()
{
//freopen("4.in","r",stdin);
//freopen("4.out","w",stdout);
int t;
scanf("%d",&t);
while(t--){
long long a,b,c;
cin>>a>>b>>c;
int flag = 0;
for(int i=0;i<=100000;i++)
{
long long res = c - a*i;
if(res==0){
puts("Yes");
flag = 1;
break;
}
if(res<0)break;
long long p = res/b;
if(p*b==res)
{
flag = 1;
puts("Yes");
break;
}
}
if(flag==0)
puts("No");
}
}
愚蠢的做法(Xiper):显然Ax+By=C,这个就是个exgcd的板子题,但是要讨论一下整数的问题,所以大力讨论一发就好了嘛。
#include <bits/stdc++.h>
using namespace std;
pair < int , int > Extend_GCD( int a , int b , int g ){
if( b == 0 )
return make_pair( g / a , 0 );
else{
pair < int , int > ret = Extend_GCD( b , a % b , g );
return make_pair( ret.second , ret.first - a / b * ret.second );
}
}
int GetRangel( int Up , int Down ){
if( Up >= 0 )
return Up / Down;
if( abs( Up ) % Down == 0 )
return Up / Down;
return Up / Down - 1;
}
int GetRanger( int Up , int Down ){
if( Up >= 0 )
return (Up + Down - 1) / Down;
return Up / Down;
}
int main( int argc , char * argv[] ){
int T;
scanf( "%d" , & T );
while( T -- ){
int a , b , c , gcd;
scanf( "%d%d%d" , & a , & b , & c );
gcd = __gcd( a , b );
if( c % gcd )
puts( "No" );
else{
pair < int , int > Sol = Extend_GCD( a , b , c );
int ranger = GetRanger( - gcd * Sol.first , b );
int rangel = GetRangel( gcd * Sol.second , a );
if( rangel >= ranger )
puts( "Yes" );
else
puts( "No" );
}
}
return 0;
}
B 喵哈哈村的括号序列
正确题解(qscqesze,xiper):这是一道栈+dp的综合运用的题目。如果我们遇到(,那么我们就把这个位置放进栈里面。如果遇到)的话,就pop掉栈顶,且栈顶就是这个)匹配的(位置,如果我们用l[i]来表示与i匹配的左括号位置的话。
那么我们令dp[i]表示以i结尾的括号序列的最长长度的方程为:dp[i]=dp[l[i]-1]+(i-l[i]+1)。
最后在所有的dp里面取个max就好了。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
string s;
stack<int> k;
int dp[maxn];
void solve(){
memset(dp,0,sizeof(dp));
while(k.size())k.pop();
cin>>s;
for(int i=0;i<s.size();i++)
{
if(s[i]=='(')
k.push(i);
else
{
if(!k.empty())
{
dp[i]=i-k.top()+1;
if(k.top()>0)
dp[i]+=dp[k.top()-1];
k.pop();
}
}
}
int ans1=0;
for(int i=0;i<s.size();i++)
if(dp[i]>ans1)
ans1=dp[i];
cout<<ans1<<endl;
}
int main(){
int t;
scanf("%d",&t);
while(t--)
solve();
}
C 喵哈哈村的魔法石(II)
题解(qscqesze):一道背包dp,原题来自于atcoder的一场beginner的比赛D题。
dp[i][j]表示能量用了i的人力,j的地力的最小花费。
那么显然就是dp[i][j] = min(dp[i][j],dp[i-a[i]][j-b[i]]+c[i])。
注意,这是一个01背包,所以你得倒着枚举这个状态,以保证状态不会覆盖。(或者你直接三维状态表示也可以。)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int dp[MAXN][MAXN],n,ma,mb,a[MAXN],b[MAXN],c[MAXN];
void init(){
for(int i=0;i<MAXN;i++)
for(int j=0;j<MAXN;j++)
dp[i][j]=1<<30;
dp[0][0]=0;
}
int main(){
//freopen("2.in","r",stdin);
//freopen("2.out","w",stdout);
int t;
scanf("%d",&t);
while(t--){
init();
scanf("%d%d",&n,&ma);
for(int i=0;i<n;i++)
scanf("%d%d%d",&a[i],&b[i],&c[i]);
for(int i=0;i<n;i++){
for(int j=MAXN-1;j>=0;j--){
for(int k=MAXN-1;k>=0;k--){
if(j>=a[i]&&k>=b[i]){
dp[j][k]=min(dp[j-a[i]][k-b[i]]+c[i],dp[j][k]);
}
}
}
}
int Ans = 1<<30;
for(int i=1;i<MAXN;i++){
for(int j=1;j<MAXN;j++){
if(i%j==0&&i/j==ma){
Ans = min(Ans,dp[i][j]);
}
}
}
if(Ans!=1<<30)
cout<<Ans<<endl;
else
cout<<"ShenBaoBao GG"<<endl;
}
}
D 喵哈哈村的赛马比赛
原题是2017网易雷火游戏研发部门笔试题最后一题。
正常做法(Xiper):算每头马对答案的贡献,不妨设有f(i),考虑一个排列就是他们之间的速度关系,第i头马对答案有贡献当且仅当前面的马速度都比他小,那么方案数就是((i-1)!(n-i)!C(n,i))/(n!),化简之后,就是1/i。
更加通俗的语言(CS_LYJ1997):最后一匹马肯定会留下来,所以为1,倒数第二匹马要留下来,得比最后一匹马速度快,就是1/2,最前面的马得是n匹马中最快的才能留下来,就是1/n。
#include <bits/stdc++.h>
using namespace std;
int main( int argc , char * argv[] ){
int T;
scanf( "%d" , & T );
while( T -- ){
int n ;
scanf( "%d" , & n );
double Answer = 0;
for(int i = 1 ; i <= n ; ++ i)
Answer += 1.0 / ( double ) i;
printf( "%.4lf\n" , Answer );
}
return 0;
}
智障做法(qscqesze):把这道题分为两个部分,A为所有情况最后马匹剩下来的和,B为总方案数。B显然就是n!,A我们打个表,然后去找规律(oeis),发现是一个递推的式子。然后我们掏出java写个高精度就完了嘛。
import java.util.*;
import java.math.*;
public class Main {
static int n, t;
static BigDecimal a[]=new BigDecimal[1002];
static BigDecimal b[]=new BigDecimal[1002];
static double c[] = new double[1002];
static public void main(String args[]) {
Scanner IN=new Scanner(System.in);
b[0]=BigDecimal.valueOf(1);
a[0]=BigDecimal.valueOf(0);
for(int i=1;i<=1000;i++)
b[i]=b[i-1].multiply(BigDecimal.valueOf(i));
for(int i=1;i<=1000;i++)
{
a[i]=a[i-1].multiply(BigDecimal.valueOf(i));
a[i]=a[i].add(b[i-1]);
}
t=IN.nextInt();
for(int cas=1;cas<=t;cas++){
n=IN.nextInt();
double l = 0,r = 10;
for(int i=0;i<50;i++){
double mid = (l+r)/2.0;
if(b[n].multiply(BigDecimal.valueOf(mid)).compareTo(a[n])>=0){
r=mid;
}else
l=mid;
}
String double_str = String.format("%.4f", l);
System.out.println(double_str);
}
}
}
E 喵哈哈村的海报问题
该题目是qscqesze在大一的时候出的,但是由于当时年幼无知,标程写错了,现在掏出来改了一下标程,然后就扔了出来。
题解1(qscqesze):
首先离散化。然后我们将所有的操作按照时间倒序排序,即t大的在前面,t小的在后面。那么显然如果这个节点被操作了之后,就不会再被更新了。
暴力显然就是O(nmk)的复杂度。
于是我们用并查集来优化一下,每一行,我们暴力处理。将fa指向每一行下一个未操作的格子。初始化是指向自己的。操作完之后,就指向下一个。这样的话,由于每个格子就只会访问一次,那么复杂度就变为了O(nmα(k)+mk),就过了。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2005;
struct node
{
int x1,y1,x2,y2,t,c;
};
int n,m,c;
int k;
node a[maxn];
vector<int> q1;
vector<int> q2;
map<int,int> H1;
map<int,int> H2;
int fa[2010][2010];
int mp[2010][2010];
map<int,int> flag;
int ans;
bool cmp(node b,node c)
{
return b.t>c.t;
}
int fi(int x,int y)
{
if(y!=fa[x][y])
fa[x][y]=fi(x,fa[x][y]);
return fa[x][y];
}
int main()
{
scanf("%d%d%d",&n,&m,&c);
q1.push_back(n);
q2.push_back(m);
q1.push_back(1);
q2.push_back(1);
scanf("%d",&k);
for(int i=0;i<k;i++)
{
scanf("%d%d%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2,&a[i].c,&a[i].t);
q1.push_back(a[i].x1);
q1.push_back(a[i].x2);
q2.push_back(a[i].y1);
q2.push_back(a[i].y2);
}
a[k].x1=1,a[k].y1=1,a[k].x2=n,a[k].y2=m,a[k].c=c,a[k].t=0;
sort(a,a+k+1,cmp);
sort(q1.begin(),q1.end());
sort(q2.begin(),q2.end());
q1.erase(unique(q1.begin(),q1.end()),q1.end());
q2.erase(unique(q2.begin(),q2.end()),q2.end());
for(int i=0;i<q1.size();i++)
H1[q1[i]]=i+1;
for(int i=0;i<q2.size();i++)
H2[q2[i]]=i+1;
for(int i=1;i<=q1.size()+3;i++)
for(int j=1;j<=q2.size()+3;j++)
fa[i][j]=j;
for(int p=0;p<=k;p++)
{
for(int i=H1[a[p].x1];i<=H1[a[p].x2];i++)
{
int now = H2[a[p].y1];
while(now<=H2[a[p].y2]){
if(!mp[i][now]){
mp[i][now]=a[p].c;
if(!flag[a[p].c])
ans++;
flag[a[p].c]=1;
}
fa[i][now]=now+1;
now = fi(i,now);
}
}
}
printf("%d\n",ans);
}
题解2(xiper):修改操作等价于是二维区间的每个点取MAX,MAX的第一维是时间,第二维是颜色,离线下来,对于第一维从左到右扫,维护好现在的Y上面的线段,每个每个X,再对Y离线,开始搞。两个离线暴力搞。
Xiper说还有Klog的做法,但是他现在一直没抠出来,还在僵硬中。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4000 + 10;
int n , m , c , occurance[maxn] , Answer ;
struct Operation{
int l , r , tp ;
pair < int , int > value;
Operation( int l , int r , int tp , pair < int , int > value ):
l( l ) , r( r ) , tp( tp ) , value( value ){}
};
struct Line{
int l , r ;
pair < int , int > value;
Line( int l , int r , pair < int , int > value) : l ( l ) , r ( r ) , value( value ){}
friend bool operator < (const Line & a , const Line & b){
return a.l < b.l || (a.l == b.l && a.r < b.r) || (a.l == b.l && a.r == b.r && a.value < b.value);
}
};
set < Line > li;
priority_queue < pair < int , int > > os;
short cnt[1005][1005],inq[1005][1005];
map < int , vector < Operation > > seq;
vector < int > sx , sc , sy , st;
vector < pair < int , int > > Add[maxn] , Del[maxn];
int main( int argc , char * argv[] ){
scanf( "%d%d%d" , & n , & m , & c );
seq[1].push_back( Operation( 1 , m , 1 , make_pair( 0 , c ) ) );
seq[n].push_back( Operation( 1 , m , -1 , make_pair( 0 , c ) ) );
int K;
scanf( "%d" , & K );
for(int i = 1 ; i <= K ; ++ i){
int x1 , y1 , x2 , y2 , colour , time ;
scanf( "%d%d%d%d%d%d" , & x1 , & y1 , & x2 , & y2 , & colour , & time );
seq[x1].push_back( Operation( y1 , y2 , 1 , make_pair( time , colour ) ) );
seq[x2].push_back( Operation( y1 , y2 , -1 , make_pair( time , colour ) ) );
}
for(auto & ir : seq)
for(auto & it : ir.second )
sc.push_back( it.value.second ) ,
st.push_back( it.value.first ) ,
sy.push_back( it.l ) ,
sy.push_back( it.r ) ;
sort( sc.begin() , sc.end() );
sort( st.begin() , st.end() );
sort( sy.begin() , sy.end() );
int sclen = unique( sc.begin() , sc.end() ) - sc.begin();
int sylen = unique( sy.begin() , sy.end() ) - sy.begin();
int stlen = unique( st.begin() , st.end() ) - st.begin();
for(auto & ir : seq)
for(auto & it : ir.second )
it.value.second = lower_bound( sc.begin() , sc.begin() + sclen , it.value.second ) - sc.begin(),
it.value.first = lower_bound( st.begin() , st.begin() + stlen , it.value.first ) - st.begin(),
it.l = lower_bound( sy.begin() , sy.begin() + sylen , it.l ) - sy.begin(),
it.r = lower_bound( sy.begin() , sy.begin() + sylen , it.r ) - sy.begin();
for(auto & ir : seq){
int x = ir.first;
auto & f = ir.second;
for(auto it : f)
if( it.tp == 1 )
li.insert( Line( it.l , it.r , it.value ) );
for(int i = 0 ; i < sylen ; ++ i)
Add[i].clear(),
Del[i].clear();
for(auto it : li){
Add[it.l].push_back( it.value );
Del[it.r].push_back( it.value );
}
for(int i = 0 ; i < sylen ; ++ i){
for(auto it : Add[i]){
if(!inq[it.first][it.second]){
inq[it.first][it.second] = 1;
os.push( it );
}
++ cnt[it.first][it.second];
}
while( !os.empty() ){
pair < int , int > vq = os.top();
if( !cnt[vq.first][vq.second] ){
inq[vq.first][vq.second] = 0;
os.pop();
}else
break;
}
if( !os.empty() )
occurance[os.top().second] = 1;
for(auto it : Del[i])
-- cnt[it.first][it.second];
}
while( !os.empty() ){
pair < int , int > vq = os.top();
cnt[vq.first][vq.second] = inq[vq.first][vq.second] = 0;
os.pop();
}
for(auto it : f)
if( it.tp == -1 )
li.erase( Line( it.l , it.r , it.value ) );
}
for(int i = 0 ; i < sclen ; ++ i)
if( occurance[i] )
++ Answer;
printf( "%d\n" , Answer );
return 0;
}
E题Xiper最后做出来了,代码:
#include <bits/stdc++.h>
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
const int maxn = 2e5 + 50;
int n , m , c , K ;
struct Operation{
int x , y1 , y2 , colour , time , tp;
Operation( int x , int y1 , int y2 , int colour , int time , int tp ) :
x( x ) , y1( y1 ) , y2( y2 ) , colour( colour ) , time( time ) , tp( tp ){}
friend bool operator < ( const Operation & a , const Operation & b ){
return a.x < b.x || ( a.x == b.x && a.tp > b.tp );
}
};
vector < Operation > seq;
vector < int > sy , sc , st ;
int sylen , sclen , stlen , occurance[maxn];
struct SegmentTree{
struct Node{
int l , r , lazy , mi , mxtime , mxcolour;
set < pair < int , int > > s;
void Cal( int y ){
if( !s.empty() && mxtime >= mi && mxtime >= y )
occurance[mxcolour] = 1;
if( lazy == -2 )
lazy = max( y , mxtime );
else
lazy = min( lazy , max( y , mxtime ) );
}
void Update(){
if( s.empty() )
mxtime = mxcolour = -1;
else{
pair < int , int > last = *(--s.end());
mxtime = last.first , mxcolour = last.second;
}
}
}tree[maxn << 2];
void Build( int l , int r , int o ){
tree[o].l = l , tree[o].r = r , tree[o].lazy = -2 , tree[o].mi = -1;
if( r - l > 1 ){
int mid = l + r >> 1;
Build( l , mid , o << 1 );
Build( mid , r , o << 1 | 1 );
}
}
void ReLeaseLabel( int o ){
if( tree[o].r - tree[o].l > 1 && tree[o].lazy != -2 ){
tree[o << 1].Cal( tree[o].lazy );
tree[o << 1 | 1].Cal( tree[o].lazy );
tree[o].lazy = -2;
}
}
void Maintain( int o ){
int l = tree[o].l , r = tree[o].r ;
tree[o].mi = tree[o].mxtime;
if( r - l > 1 )
tree[o].mi = max( min( tree[o << 1].mi , tree[o << 1 | 1].mi ) , tree[o].mxtime );
}
void Modify( int ql , int qr , pair < int , int > UpdValue , int o ){
int l = tree[o].l , r = tree[o].r;
ReLeaseLabel( o );
if( ql <= l && r <= qr ){
tree[o].s.insert( UpdValue );
tree[o].Update();
}
else{
int mid = l + r >> 1;
if( ql < mid )
Modify( ql , qr , UpdValue , o << 1 );
if( qr > mid )
Modify( ql , qr , UpdValue , o << 1 | 1 );
}
Maintain( o );
}
void Delete( int ql , int qr , pair < int , int > DeleteValue , int o ){
int l = tree[o].l , r = tree[o].r;
ReLeaseLabel( o );
if( ql <= l && r <= qr ){
tree[o].s.erase( DeleteValue );
tree[o].Update();
}
else{
int mid = l + r >> 1;
if( ql < mid )
Delete( ql , qr , DeleteValue , o << 1 );
if( qr > mid )
Delete( ql , qr , DeleteValue , o << 1 | 1 );
}
Maintain( o );
}
}SegmentTree;
int getrank( int x , vector < int > & sx , int slen ){
return lower_bound( sx.begin() , sx.begin() + slen , x ) - sx.begin();
}
int main( int argc , char * argv[] ){
n = read() , m = read() , c = read() , K = read();
seq.push_back( Operation( 1 , 1 , m + 1 , c , 0 , 1 ) );
seq.push_back( Operation( n , 1 , m + 1 , c , 0 , -1 ) );
for(int i = 1 ; i <= K ; ++ i){
int x1 = read() , y1 = read() , x2 = read() , y2 = read() , c = read() , t = read();
seq.push_back( Operation( x1 , y1 , y2 + 1 , c , t , 1 ) );
seq.push_back( Operation( x2 , y1 , y2 + 1 , c , t , -1 ) );
}
for(auto & it : seq)
sy.push_back( it.y1 ) ,
sy.push_back( it.y2 ) ,
sc.push_back( it.colour ) ,
st.push_back( it.time ) ;
sort( sy.begin() , sy.end() );
sort( sc.begin() , sc.end() );
sort( st.begin() , st.end() );
sylen = unique( sy.begin() , sy.end() ) - sy.begin();
sclen = unique( sc.begin() , sc.end() ) - sc.begin();
stlen = unique( st.begin() , st.end() ) - st.begin();
for(auto & it : seq)
it.y1 = getrank( it.y1 , sy , sylen ) ,
it.y2 = getrank( it.y2 , sy , sylen ) ,
it.colour = getrank( it.colour , sc , sclen ) ,
it.time = getrank( it.time , st , stlen ) ;
SegmentTree.Build( 0 , sylen - 1 , 1 );
sort( seq.begin() , seq.end() );
for(int i = 0 ; i < seq.size() ; ){
int j = i , check = 0;
while( j < seq.size() && seq[j].x == seq[i].x ){
auto & x = seq[j];
if( x.tp > 0 )
SegmentTree.Modify( x.y1 , x.y2 , make_pair( x.time , x.colour ) , 1 );
else{
if(!check)
SegmentTree.tree[1].Cal( -1 ) ,
check = 1;
SegmentTree.Delete( x.y1 , x.y2 , make_pair( x.time , x.colour ) , 1 );
}
++ j;
}
if( j != seq.size() && j > 0 && seq[j].x - seq[j - 1].x > 1 )
SegmentTree.tree[1].Cal( -1 ) ;
i = j;
}
int Answer = 0;
for(int i = 0 ; i < sclen ; ++ i)
if( occurance[i] )
++ Answer;
printf( "%d\n" , Answer );
return 0;
}