第一场是朝鲜出题
1004 Distinct Sub-palindromes
跟榜开的这题,一开始猜是26的n次方,直接交一发wa了, 后来和队友想了半天,想到了abcabcabc,答案就是n<=3时是26的n次方, else ans = 26*25*24
1005 Fibonacci Sum
每个斐波纳契数的通项二项展开,再合并,是二项展开的套路。组合数要先预处理阶乘和阶乘逆元。
比赛时队友过的这题。
但是赛后我怎么写都T,把队友当时的AC代码交上去也T了,自闭了
1009 Leading Robots
队友写1005的时候我在想这题,想了挺久
n个机器人赛跑,每个机器人初速度为0,加速度为ai,初始位置在pi,问有多少个机器人当过第一 (第一指独一无二的最前面的那个)
容易想到下面两点
一:n*(n-1)/2的比较次数肯定是不行的,要想办法减少比较次数
二:加速度越大的最终排名一定越靠前,加速度大的人不会被加速度小的人从后方超越
考虑3个人的情况,且这3个人编号越大,起点越靠后,加速度越大,
设2号追上1号的时间 t1 ,3号追上2号的时间 t2 ,和3号追上1号的时间 t3,
若t3 < t1,即3号比2号先超过1号,ans = 2; 若t2 > t1,即2号先超过1号,然后3号超过2号,ans = 3
这里t1,t2,t3都用到了, 实际上只要用 t1和t2 或 t1和t3 就行了,
t1和t3是两个人追上同一个人分别所用的时间,感觉很难有进一步的思路,下面思考t1和t2
若t1<t2,即2号追上1号之前 3号追上了2号,3号先追上了1号,当了第一名,之后2号也无法当第一名了, 因为3号加速度比2号大,2号的存在简直像是可以被消除掉了(
若t1>t2,2号还是能当第一名的,但如果后面又来了个4号,在2号当第一之前超过了2号,2号的存在又可以被消除了,而且4号把2号消除之前,已经把3号消除了
于是可以想到一个单调栈做法,栈内保存的是每个人当第一的时刻,且单调递增, 每次加入一个加速度比栈内所有人都要大的新人,计算新人超越栈顶的时间,如果新人超越栈顶的时刻比栈顶人当第一的时刻小,就把栈顶人pop,栈顶人的存在就被消除了,如果新人超越栈顶的时刻比栈顶人当第一的时刻大,那么目前栈顶人还是能当第一,新人当第一的时刻就是新人超越栈顶的时刻,计录新人的这个时刻并把新人push入栈。如果新人在一开始的时候就是第一,那么他当第一的时刻就是0,栈内元素个数就是当过答案。
至于多人同时保持当第一的情况,就特别处理。
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const int MAXN = 5e4+7;
const long long INF = 1e18+9;
double eps = 1e-12;
struct RBT{//机器人
long long p,a;
double t = 0.0 ;
}rbt[MAXN],st[MAXN];
bool cmp(RBT x,RBT y){
if(x.a!=y.a) return y.a>x.a;
else return y.p>x.p;
}
int main()
{
int t,n,h;
cin>>t;
while(t--){
cin>>n;
int l = 0,r =0;
for(int i = 1;i<=n;i++){
scanf("%d%d",&rbt[i].p,&rbt[i].a);
rbt[i].t = 0.0; //初始化,比赛的时候就是少了这个,wa了几发
}
sort(rbt+1,rbt+n+1,cmp);
for(int i = 1;i<=n;i++){
if(r&&rbt[i].p==st[r].p&&rbt[i].a==st[r].a){//特别处理
rbt[i].t = st[r].t;
}
else while(r>l){
if(rbt[i].p>=st[r].p){
r--;
continue;
}
long long da = rbt[i].a-st[r].a;
long long dx = st[r].p-rbt[i].p;
long long dvxdv = 2 * da * dx;
double dv = sqrt((double)dvxdv);
rbt[i].t = dv/da;
if(rbt[i].t<=st[r].t+eps) {
r--;
}
else break;
}
r++;
st[r] = rbt[i];
}
int ans = r;
for(int i = 2;i <= r;i++){//把相同第一的同位同速人删掉
if(st[i-1].a==st[i].a&&st[i-1].p==st[i].p){
while(st[i].a==st[i-1].a&&st[i].p==st[i-1].p){
ans--;
i++;
}
ans--;
}
}
cout<<ans<<endl;
}
return 0;
}
1006 Finding a MEX
比赛时没做出来
把度数大于350的节点称为大节点,其他节点称为小节点,每个大节点建一个对应线段树。
求小节点的MEX就暴力求
求大节点的MEX就要用到线段树,线段树维护的是最小值,查询MEX的时候,如果左半线段的最小值为0,就往左边查询,否则往右半段查询。
每次修改值时,把该节点连接的所有大节点的线段树进行修改,要预处理每个节点连接哪些大节点
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<time.h>
using namespace std;
struct NODE{
int l, r, mi;
}tree[360][24000];
int val[360][6000];//第i个线段树上某个值出现了几次
int a[100007];//节点上的值
int d[100007];//节点的度数
int ant[100007];//节点对应第几个线段树
vector<int>graph[100007];//存图
vector<int> gg[100007];//gg[i]
bool vis[360];//小节点上的vis
int tot;
void build(int cnt,int pos,int l,int r){
tree[cnt][pos].l = l;
tree[cnt][pos].r = r;
if(l==r){
tree[cnt][pos].mi = min(val[cnt][tot],1);
tot++;
return;
}
int mid = l+r>>1;
build(cnt,pos<<1,l,mid);
build(cnt,pos<<1|1,mid+1,r);
tree[cnt][pos].mi = min(tree[cnt][pos<<1].mi,tree[cnt][pos<<1|1].mi);
}
void modify(int cnt,int pos,int p,int v){
if(tree[cnt][pos].l==tree[cnt][pos].r){
tree[cnt][pos].mi = v;
return;
}
int mid = tree[cnt][pos].l+tree[cnt][pos].r>>1;
if(p<=mid) modify(cnt,pos<<1,p,v);
else modify(cnt,pos<<1|1,p,v);
tree[cnt][pos].mi = min(tree[cnt][pos<<1].mi,tree[cnt][pos<<1|1].mi);
}
int query(int cnt,int pos,int l,int r){
if(tree[cnt][pos].l==tree[cnt][pos].r){
return tree[cnt][pos].l;
}
int mid = tree[cnt][pos].l+tree[cnt][pos].r>>1;
if(tree[cnt][pos<<1].mi==0) return query(cnt,pos<<1,l,mid);
else return query(cnt,pos<<1|1,mid+1,r);
}
int main()
{
int t, n, m, q, u, v;
cin>>t;
while(t--){
cin>>n>>m;
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
d[i] = 0;
graph[i].clear();
gg[i].clear();
ant[i] = 0;
}
for(int i = 1;i<360;i++){
for(int j = 0;j<6000;j++){
val[i][j] = 0;
}
}
for(int i = 1;i <= m;i++){
scanf("%d%d",&u,&v);
graph[u].push_back(v);
graph[v].push_back(u);
d[u]++;
d[v]++;
}
for(int i = 1;i<=n;i++){//存每个点连接的大节点
for(int j = 0;j<graph[i].size();j++){
int po = graph[i][j];
if(d[po]>350) gg[i].push_back(po);
}
}
int cnt = 0;
for(int i = 1;i <= n;i++){//找出度数>350的节点建树
if(d[i]>350){
cnt++;
ant[i] = cnt;
tot = 0;
for(int j = 0;j < d[i];j++){
int po = graph[i][j];
if(a[po]>d[i]){
val[cnt][d[i]]++;
}
else val[cnt][a[po]]++;
}
build(cnt,1,0,d[i]);
}
}
cin>>q;
int op,x;
for(int i = 1;i <= q;i++){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&u,&x);
//printf(" %d\n",gg[u].size());
for(int j = 0;j<gg[u].size();j++){
int po = gg[u][j];
int lo = a[u];
if(lo>d[po]) lo = d[po];
val[ant[po]][lo]--;
if(!val[ant[po]][lo]){
modify(ant[po],1,lo,0);
}
lo = x;
if(lo>d[po]) lo = d[po];
if(!val[ant[po]][lo]){
modify(ant[po],1,lo,1);
}
val[ant[po]][lo]++;
}
a[u] = x;
}
else{
scanf("%d",&u);
if(d[u]>350){
printf("%d\n",query(ant[u],1,0,d[u]));
}
else{
for(int j = 0;j<=d[u];j++) vis[j] = false;
for(int j = 0;j<graph[u].size();j++){
int po = graph[u][j];
int lo = a[po];
if(lo>d[u]) lo = d[u];
vis[lo] = true;
}
for(int j = 0;j<=d[u];j++){
if(!vis[j]){
printf("%d\n",j);
break;
}
}
}
}
}
}
return 0;
}