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(按照预计题目难度)
读入字符串,把末尾的句号改成感叹号。多组数据。
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; }
求序列中有几个不同的数字。
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; }
给出$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; }
(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; }
这个题目一定要看原文(我除了扯淡两句话看的懂以外剩下就是看样例猜题面了)
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; }
给出一序列$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; }
给出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; }
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; }
对于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; }