HZNU ACM一日游 2019.3.17 【2,4,6-三硝基甲苯(TNT)】

Travel Diary

早上8:00到HG,听说hjc20032003在等我。

然后他竟然鸽我...最后还是勉强在8:30坐上去偏僻的HZNU的地铁。

到文新,然后带上fjl,打滴滴,一行人来到了HZNU。

早上模拟赛,先疯狂打期望概率为$\frac{1}{10}$的T1,然后26发以后过了。

后面爆推T2两圆面积交式子,然后少考虑特判情况WA了几发,后面没时间了就滚去吃饭了。

话说T3真的毒瘤,主要是英语阅读比较难(整整两页纸!!!)。

下午迟到1分钟开始模拟赛,某队伍在0:07就A了第一题?我们错过了什么。。。

先hjc上A,我想B,然后没想法,就滚去看D了,然后发现D是一个非常裸的set题,就随手码掉了。

这个时候2A,然后fjl说他想上个题,然后拿了J题,写个公式交了一发WA!,然后我检查一发,发现没开long long

于是补上,Accepted!

45分钟,作为菜鸡队的我们,成功拿下送分题ADJ。

然后我在1小时发现H题可做,就是特判1,2,3的三种情况,然后直接排序统计即可,上手20分钟1A H题。

然后hjc说他B题知道怎么做了。。就是最劣情况斐波那契数列,然后其他暴力搞,由hjc写B然后我继续翻看题面。

也没什么想法,发现K过的人比较多,但是很多次提交,一定是坑题。写K,发现K并不难想出,直接$ O(n^3+q log_2 n^3) $的复杂度就正确了。

hjc打B题比较迅速,对拍、检查,交了一发,WA!仔细再拍也没有发现什么问题,hjc认为他的结论是正确的然后开大空间,开long long,交了1发。

还是WA!此时没办法,打印代码,让我写K题,迅速的写好K题,迅速过样例,交了一发,WA!然后发现实数比较大小有精度误差,手写二分再交WA!

然后只能考虑整数二分,二分上端点的时候使用向上取整,二分下端点的时候向下取整,这样二分里面就可以做到整数比较了,然后再交还是WA!

无奈,打印代码,弃坑。

hjc说他想好G题单调队列dp,打了一发,秒过样例,交一发WA!,然后打对拍没拍出问题,后来发现没开srand,后来又发现

打错一个字母,迅速改,拍,发现暴力错了23333,交!Accepted!

此时考试结束还有1小时,我们手里有5题,然后B,K写了没过,在看B的时候我喵一眼M,发现是个博弈最后猜出sg函数,WA 1发由于筛质数筛小了。在比赛快结束的时候,我们考虑一定是B数据出锅了然后疯狂特判,最后8发,终于A了。。

还有15分钟,我看了I了,写了一发过了样例,卡时交,可惜Wa了,后来发现需要考虑反映射,其实I是个简单题。

最终带点小遗憾拿了ABDGHJM 7题,hjc 3题,ljc 4题,fjl 1题。hjc比较巨!

在参赛的266队伍中拿了rank 38-45 (按罚时是44名),获得7个气球!!!

Tips:反正我们还是太菜(主要是HGOI某队少了rym巨佬,他们只打了5题)

Problem & Solution(按照预计题目难度)

A. Little Sub and Applese

读入字符串,把末尾的句号改成感叹号。多组数据。

Sol : 考察选手会不会使用DEV C++,考察选手能否正确使用评测OJ。

复杂度$ O(T \times length) $

注意考虑多组数据还有space,那么使用getline(cin,s)读入字符串,注意该函数和scanf()一样有返回值。

# include <cstdio>
# include <iostream>
# include <cstring>
using namespace std;
string s;
int main()
{
    while (getline(cin,s)) {    
        int len=s.length();
        if (s[len-1]=='.') s[len-1]='!';
        cout<<s<<'\n';
    }
    return 0;
}
A.cpp

D. Little Sub and Balloons

求序列中有几个不同的数字。

Sol :考察选手会不会使用STL set,本人想不出更简单的算法。

复杂度$ O(n log_2 n) $

# include<iostream>
# include<cstdio>
# include<set>
using namespace std;
set<int>s;
int main()
{
    int n; scanf("%d",&n);
    for (int i=1;i<=n;i++){
        int t; scanf("%d",&t);
        s.insert(t);
    }
    cout<<s.size()<<'\n';
    return 0;
 } 
D.cpp

J. Little Sub and Apples

给出$n,k,t$ 表示$n$个苹果,吃$k$天,每天吃$ min\{ t , rest \} $个苹果,询问剩余几个苹果。

Sol: 比较$t \times k $和$n$的大小,若$t \times k \leq n$输出$n-t \times k$否则输出$0$

        记得脑子清楚点开long long,不开long long WA一发!

   复杂度 $ O(1) $

# include <cstdio>
# include <cstring>
# include <iostream>
using namespace std;
# define int long long
signed main()
{
    int n,k,t; cin>>n>>k>>t;
    cout<<((k*t<n)?(n-k*t):(0))<<'\n';
    return 0;
}
J.cpp

 B. Little Sub and Triples

(a,b,c)记为三元组,给定一个函数$ f(a,b,c)= \frac{e^a \times e^b}{e^c} $,给出一个序列$ A $和$ m $个询问$l , r$,即求$i,j,k \in [l,r]且i\neq j\neq k$是否存在三元组($A_i,A_j,A_k$)

满足$A_i \leq  A_j \leq A_k$且使得$f(A_i,B_i,C_i) > 1$成立。

对于每一个询问输出"YES"表示有,"NO"表示没有。

对于100%的数据满足 $n \leq 2\times 10^5 , A_i \leq 2^{32}-1$

Sol :一道好题,考场上hjc想出正解。

题意就是求出一序列$[l,r]$是否存在三个元素,可以组成三角形。

我们知道,组成三角形的条件是" 两个较小边之和大于最大边 ",

考虑缩放,放宽三角形组成条件为" 两个较小边之和大于等于最大边 ",

在此基础上,取最优情况是" 两个较小边之和等于最大边 ",满足斐波那契数列。

考虑斐波那契数列第多少项大于等于$2^{32}-1$,打表可知是56项。

那么对于一个区间满足$r-l+1 > 56$就可以知道无法构造一个"NO"的数据了。

我们知道对于前两个数,必然大于等于$1,1$如果每次恰好不能组成三角形,为了构造"NO"的数据,那么就必须按照斐波那契数列走下去。
如果前两个数更大或者每次不能组成三角形,那么事实上会比斐波那契数列走下去走的更少。

若考虑最差情况区间前两个数位$1,1$,每次恰好不能组成三角形,按照斐波那契数列走下去,不出56项就会和题目$A_i$的范围大。

剩下的若干个数,由鸽巢原理可知,必然可以和前面两个数都可以组成一个三角形,自然是"YES"得证。

接下来,考虑区间长度小于$56$情况,不满足上述性质,直接暴力排序,顺序扫,

若$\exists A_i + A_{i+1} > A_{i+2} , 1 \leq i \leq n-2$那么三元组($A_i , A_{i+1} , A_{i+2} $)符合条件,就是"YES"

否则就是"NO"。

Tips:做加法的时候会爆int,所以采用做减法(就不开long long气不气?)

复杂度 $ O(kn) ,k=56$

# include <cstdio>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
const int N=2e5+10;
int t[65],a[N],n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    while (m--) {
        int l,r; scanf("%d%d",&l,&r);
        if (r-l+1>56) { puts("YES"); continue;}
        int cnt=0; bool flag=false;
        for (int i=l;i<=r;i++) t[++cnt]=a[i];
        sort(t+1,t+1+cnt);
        for (int i=1;i<=cnt-2;i++)
         if (t[i]>t[i+2]-t[i+1]) {
             flag=true;break;
         }
        puts(flag?"YES":"NO");
    } 
    return 0;
}
B.cpp

 I. Little Sub and Enigma

这个题目一定要看原文(我除了扯淡两句话看的懂以外剩下就是看样例猜题面了)

26个小写字母有一组双射,求从读入的数据中能观察或推导出的关系。

Sol : 考场完全不知道是双射,然后...时间又来不及白白放弃简单的I题...

直接开map模拟正反映射,考虑双向映射合法的充要条件是1不能对多。

当正反映射都是25个成立的时候,那么第26组映射是唯一确定的。(由于只有小写字母)

复杂度 $ O(n log_2 n) $

# include <cstdio>
# include <cstring>
# include <iostream>
# include <algorithm>
# include <map>
using namespace std;
string s,t;
map<char,char>mp1,mp2;
int bo1['z'+1],bo2['z'+1];
int main()
{
    getline(cin,s);int lens=s.length(); 
    getline(cin,t);int lent=t.length();
    int cnt1=0,cnt2=0;
    for (int i=0;i<lens;i++) {
        char ss=s[i],tt=t[i];
        if (mp1.count(ss)!=0) {
            if (mp1[ss]!=tt) {
                puts("Impossible"); return 0;
            }
        } else {
              mp1[ss]=tt;
              bo1[ss]=1; cnt1++;
          }      
        if (mp2.count(tt)!=0) {
            if (mp2[tt]!=ss) {
                puts("Impossible"); return 0;
            }
        } else {
            mp2[tt]=ss;
            bo2[tt]=1; cnt2++;
        }
    }
    if (cnt1==25&&cnt2==25) {
        char op1,op2;
        for (char i='a';i<='z';i++)  
         if (!bo1[i]) { op1=i; break;}
        for (char i='a';i<='z';i++)
         if (!bo2[i]) { op2=i; break;} 
        mp1[op1]=op2; 
    }
    for (int i='a';i<='z';i++)
     if (mp1.count(i)) printf("%c->%c\n",i,mp1[i]);
    return 0;
}
I.cpp

 H. Little Sub and Counting

给出一序列$A$,对于每个$A_i$,求有多少个$j$,满足${A_i} ^ {A_j} > {A_j} ^ {A_i}$。

对于100%的数据 $n \leq 10^5 , A_i \leq 2^{32}-1$

Sol : 这道题考场是我A的。挺简单的,没有想象那么难。

考虑举一些例子,发现指数大的数字可能比指数小的数字都大,有没有反例呢。取1,2,3,一取一个准。

后来我尝试证明$a^b > b^a$的条件,显然$ ln $降次$ln(a^b) > ln(b^a)$,即$bln a > a ln b$由于$a,b > 0$移项,

得$\frac{ln a}{a} > \frac{ln b}{b} $考虑$ y=\frac{ln x}{x} $的单调性。

对函数$ y=\frac{ln x}{x} $求导可知$y' = \frac{\frac{1}{x} \times x - lnx\times 1}{x^2} = \frac{1-lnx}{x^2}$

令$y'=0$得$x = e$ 那么 就以$e=2.7$作为基准,有3个特例$1,2,3$就打表特判贡献即可。

复杂度 $ O(n log_2 n) $

# include <cstdio>
# include <cstring>
# include <iostream>
# include <algorithm>
# include <map>
# define int long long
using namespace std;
const int N=1e5+10;
int a[N],t[N],cnt,n,ans[N];
int find(int x)
{
    int l=1,r=cnt,ans=-1;
    while (l<=r) {
        int mid=(l+r)/2;
        if (t[mid]>=x) r=mid-1,ans=mid;
        else l=mid+1;
    }
    if (ans==-1) return 0;
    return cnt-ans+1;
}
signed main()
{
    scanf("%lld",&n);
    int cnt1,cnt2,cnt3;cnt1=cnt2=cnt3=0;
    for (int i=1;i<=n;i++) {
        scanf("%lld",&a[i]);
        if (a[i]==1) cnt1++;
        else if (a[i]==2) cnt2++;
        else if (a[i]==3) cnt3++;
        if (a[i]>3) t[++cnt]=a[i];
    }
    sort(t+1,t+1+cnt);
    for (int i=1;i<=n;i++) {
        if (a[i]==1) printf("0 "); 
        else if (a[i]==2) printf("%lld ",cnt1+find(5)); 
        else if (a[i]==3) printf("%lld ",cnt1+cnt2+find(4));
        else printf("%lld ",cnt1+find(a[i]+1));
    }
    puts("");
    return 0;
}
H.cpp

 K. Little Sub and Triangles

给出n个整数坐标点,给出$m$个询问,求这些点中可以互相构成三角形或者点的面积在$ [l,r]$的有多少个。

对于100%的数据$n \leq 250 , m\leq 10^5$

Sol : 本题是我打的,使用海伦公式疯狂WA。由于求距离,三边求面积开2次根号,精度误差太大。

尽管塞入容器二分的时候考虑上取整枚举上界,下取整枚举上界避免小数比较大小,但是还是由于比较强的数据WA了。

没有想到皮克定律(个点三角形面积最小分度值为0.5,证明的话就弄个坐标矩形框三角形即可)。

考虑怎么求格点三角形面积,使用向量外积即叉乘。可以推倒结论

对于$ A(x_1,y_1), B(x_2,y_2), C(x_3,y_3)$围成面积$S = | \frac{1}{2} \times {(x_1 y_2 - x_2 y_1)+(x_2 y_3 - x_3 y_2)+(x_3 y_1-x_1 y_3)} |$

然后全部乘以2,就可以全整数运算了。

复杂度 $ O(n^3 + m log_2 n^3) $

# include <cstdio>
# include <iostream>
# include <cstring>
# include <algorithm>
# define int long long
using namespace std;
const int N=250+10;
struct rec{
    int x,y;
}a[N];
int t[N*N*N/6],n,m,cnt;
int Fun(int x)
{
    if (x<0) return -x;
    else return x;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=n;i++) 
     scanf("%lld%lld",&a[i].x,&a[i].y);
    for (int i=1;i<=n;i++)
     for (int j=i+1;j<=n;j++)
      for (int k=j+1;k<=n;k++)
       t[++cnt]=Fun((a[i].x*a[j].y-a[j].x*a[i].y)+(a[j].x*a[k].y-a[k].x*a[j].y)+(a[k].x*a[i].y-a[i].x*a[k].y));
    sort(t+1,t+1+cnt); 
    while (m--) {
        int l,r; scanf("%lld%lld",&l,&r);
        l<<=1; r<<=1;
        int L=lower_bound(t+1,t+1+cnt,l)-t;
        int R=upper_bound(t+1,t+1+cnt,r)-t-1;
        if (R<L) puts("0");
        else printf("%lld\n",R-L+1); 
    }
    return 0;
}
K.cpp

 M. Little Sub and Johann

石子游戏,要不取一堆,要不取x个且gcd(x,该堆石子个数)=1,问谁会赢。

考虑打表求sg函数,若x是质数那么$ sg(x)=\max\limits_{i=0}^{x-1} \{ sg(i) \} +1 $

若x是合数,那么$sg(x)=sg(y),y为x最小质因数$

所以一边筛法就可以求出sg函数了。下面会给出证明:

#include <cstdio>
#include <iostream>
using namespace std;
const int N=1000000;
int t,n,sg[N+50],k,ans,x;
void find_prime()  
{
    sg[1]=k=1;
    for (int i=2;i<=N;i++)
    {
        if (sg[i]) continue;  
        sg[i]=++k; 
        for (int j=i;j<=N;j+=i)
         if (!sg[j]) sg[j]=k; 
    }
    return;
}
int main()
{
    scanf("%d",&t);
    find_prime();
    while (t--)
    {
        scanf("%d",&n); ans=0;
        for (int i=1;i<=n;i++)  scanf("%d",&x),ans^=sg[x];  
        if (ans) printf("Subconscious is our king!\n");
         else printf("Long live with King Johann!\n");
    }
    return 0;
}
M.cpp

 G. Little Sub and Piggybank

对于n个物品有价格w[i],快乐值v[i]。

每天能往存钱罐加任意实数的钱,每天放的钱数需要小于等于前一天放的钱数。

如果某 一天的钱数恰好等于那天的特价商品,则可以买,获得那个物品的快乐值,

求最后的最大快乐值。

对于100%的数据$ n \leq 2000 $

Sol :  本题考场是hjc 2A的,考场想了个单调队列+二分维护一个转移。 

  f[i][j]表示从i天开始(第i天rest 0块钱),到第j天买(买第j个商品),最大happiness值

  显然$\max\limits_{j=1}^{n} \{ f[1][j] \}$就是答案。

  对于朴素转移,考虑i->j->k合法,那么有$ f[i][j]=\max\{f[j+1][k]\}+v[i] $

  考虑i->j->k合法的条件,那么就是一个贪心,即为了买第j个物品,那么在[i+1,j]每天加入$\frac{w[j]}{j-i}$块钱。

  同理,为了买第k个物品那么在[j+1,k]每天加入$\frac{w[k]}{k-j}$块钱。

  所以转移的条件就是$\frac{w[j]}{j-i} \geq \frac{w[k]}{k-j}$

  考虑每个确定的i,维护$ f[i][k] $一个单调栈,只在队尾插入,队尾弹出,且满足决策值$ f[i][k] $单调递减,k和i之间买的物品每天平摊价格$\frac{w[k]}{k-i}$单调递减的单调栈。

  对于每次转移,只需要在单调栈里面二分出一个符合条件的每天平摊价格,从对应的决策值转移即可。

  时间复杂度$ O(n^2 log_2 n)$

  事实上,本题还有$O(n^2)$的dp做法,是使用后缀max维护的。

  令f[i][j]表示最后两个被买的物品为i号j号($i<j$),也可以为空但要特殊判断。

  考虑p->j->i转移合法。根据上面的分析,可以知道$p\geq \left \lceil j-\frac{(i-j) \times w[j] }{w[i]} \right \rceil$

  转移是$f[i][j]=\max\limits_{p=\left \lceil j-\frac{(i-j) \times w[j] }{w[i]} \right \rceil}^{i-1} \{ f[p][j] \}+v[i] $

  考虑维护后缀和g[i][j]维护对于一个确定的j,f[i][j]的后缀最大值,即$ g[i][j]=\max\limits_{k=i}^{j-1} \{f[k][j]\} $

  那么上面的转移就可以转化为$f[j][i]=g[p][j]+v[i] , p= \left \lceil j-\frac{(i-j) \times w[j] }{w[i]} \right \rceil $

  注意一些细节,如果p对于全局合法(即$ p\leq  0 $),那么令p=1,但会造成一些非法的转移,所以需要设初始值全局为-INF,排除一些奇怪的不合法情况。

  特判只有2个的初始情况,即仅取(j,i)时的初始值,即当$f[j][i]=v[j]+v[i] , (\frac{w[j]}{j} \geq \frac{w[i]}{i-j})$

  调了好久的代码....

  复杂度$ O(n^2) $

# include <cstdio>
# include <cstring>
# include <iostream>
# include <cmath>
# define int long long
using namespace std;
const int N=2e3+10;
int w[N],v[N],n,ans;
int f[N][N],g[N][N];
signed main()
{
    scanf("%lld",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
    for (int i=1;i<=n;i++) scanf("%lld",&v[i]);
    memset(g,~0x3f,sizeof(g));
    memset(f,~0x3f,sizeof(f));
    for (int i=1;i<=n;i++)
     for (int j=i-1;j>=1;j--)
      {
          int p=max((int)ceil((double)j-(double)(i-j)*w[j]/(double)w[i]),1ll);
          if ((i-j)*w[j]>=w[i]*j) f[j][i]=max(f[j][i],v[i]+v[j]);
          f[j][i]=max(f[j][i],g[p][j]+v[i]);
          g[j][i]=max(g[j+1][i],f[j][i]);
          ans=max(ans,f[j][i]);
      }
    cout<<ans<<'\n';
    return 0;
}
G.cpp

 

posted @ 2019-03-18 18:34  ljc20020730  阅读(432)  评论(0编辑  收藏  举报