补题链接
我无颜……
[F]
分析:
关于“排名”没转化过来。
实际上排名 = 小于等于自己的数的个数。那么可以针对每一个比自己小的数计算,考虑它们各自出现在多少个区间中,位置在自己左边和右边的分开计算。再加上自己的答案。
于是用树状数组就能做了。时间复杂度 $ O(nlogn) $ 。
代码如下
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int const N=5e5+5;
int n,a[N];
ll ans[N],tl[N<<1],tr[N<<1];
ll ql(int x){ll ret=0; while(x){ret+=tl[x]; x-=(x&(-x));} return ret;}
void addl(int x,int v){while(x<=n){tl[x]+=v; x+=(x&-x);}}
ll qr(int x){ll ret=0; while(x){ret+=tr[x]; x-=(x&(-x));} return ret;}
void addr(int x,int v){while(x<=n){tr[x]+=v; x+=(x&-x);}}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ans[i]=(ll)i*(n-i+1);
ans[i]+=ql(a[i])*(n-i+1);
addl(a[i],i);
}
for(int i=n;i;i--)
{
ans[i]+=qr(a[i])*i;
addr(a[i],n-i+1);
}
for(int i=1;i<=n;i++)
printf("%lld\n",ans[i]);
return 0;
}
[G]
分析:
$ 2*10^5 $ 是可以考虑 $ O( n \sqrt{n} ) $ 的做法的。此题就可以按 $ |T_i| $ 是否大于 $ \sqrt{ |S| } $ 分开做。
$ |T_i| \leq \sqrt{ |S| } $ 的,可以预处理出 $ f[len][i][c] $ 表示 $ mod(len) $ 下 $ i $ 位置上字符 $ c $ 在 $ S $ 中出现的次数。预处理的复杂度是 $ O( n \sqrt{n} ) $ ,每个这样的 $ T_i $ 计算答案的复杂度是 $ O( |T_i| ) $ ,即 $ O( \sqrt{n} ) $ 。
$ |T_i| \geq \sqrt{ |S| } $ 的,直接做即可。每个这样的 $ T_i $ 计算答案的复杂度是 $ O(n) $ ,但是这样的串不超过 $ \sqrt{n} $ 个。
然后总的复杂度是 $ O( n \sqrt{n} ) $ 。这种套路掌握得还是不熟……
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const N=2e5+5,M=505;
int n,m,ans[N],f[M][M][30];
char s[N],t[N];
void Init()
{
m=500;
for(int i=1;i<=n;i++)
{
int c=s[i]-'a';
for(int j=1;j<=m;j++) f[j][(i-1)%j][c]++;
}
}
int main()
{
scanf("%s",s+1); n=strlen(s+1);
Init();
int q; scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%s",t+1); int len=strlen(t+1);
int ans=0;
if(len<=m)
{
for(int j=1;j<=len;j++)
ans+=f[len][j-1][t[j]-'a'];
}
else
{
int k=1;
for(int i=1;i<=n;i++)
{
ans+=(s[i]==t[k]); k++;
if(k>len)k=1;
}
}
printf("%d\n",n-ans);
}
return 0;
}
[H]
分析:
把能否杀死对方转化成两方各自参数(攻击*血量)的比较,就可以确定要选定的随从是哪个(攻击*血量最大或次大的才有能力杀死其他所有人);
选定的随从杀死其他所有人的过程中受到的伤害是固定的,其中还会有个最大的伤害。根据这个就能判断能否达成题目要求了。
注意判断攻击等于0的情况。
然而一直WA在第45个点,不知为啥。先放在这吧……
代码如下
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
int const N=1e5+5;
struct Nd{
ll a,h;
}a[N];
int n;
bool cmp(Nd x,Nd y){return (x.a*x.h==y.a*y.h) ? x.a>y.a : x.a*x.h>y.a*y.h;}
ll cal(ll h,ll a)
{
if(a==0)return -1;
return (h/a)+(h%a!=0);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&a[i].a,&a[i].h);
sort(a+1,a+n+1,cmp);
if(n==1){puts("NO"); return 0;}
// if(n>=3 && (a[1].a*a[1].h==a[2].a*a[2].h) && (a[2].a*a[2].h==a[3].a*a[3].h)){puts("NO"); return 0;}
if(a[1].a==0 || a[2].a==0){puts("NO"); return 0;}
ll mx=0,all=0; int p; //最大伤害,总伤害,最大伤害者
for(int i=2;i<=n;i++)
{
ll t=cal(a[i].h,a[1].a);
all += t*a[i].a;
if(t*a[i].a>=mx)mx=t*a[i].a,p=i;
}
// if(a[1].h-all<=0 && a[1].h-all+mx>0 && cal(a[1].h-all+mx,a[p].a)==cal(a[p].h,a[1].a))
// {puts("YES"); return 0;}
if(a[1].h-all<=0 && a[1].h-all+mx>0)
{
for(int i=2;i<=n;i++)
{
ll t=cal(a[i].h,a[1].a);
if(t*a[i].a==mx && cal(a[1].h-all+mx,a[i].a)==t)
{puts("YES"); return 0;}
}
}
mx=0; all=0; //最大伤害,总伤害
for(int i=1;i<=n;i++)
{
if(i==2)continue;
ll t=cal(a[i].h,a[2].a);
all += t*a[i].a;
if(t*a[i].a>=mx)mx=t*a[i].a,p=i;
}
// if(a[2].h-all<=0 && a[2].h-all+mx>0 && cal(a[2].h-all+mx,a[p].a)==cal(a[p].h,a[2].a))
// {puts("YES"); return 0;}
if(a[2].h-all<=0 && a[2].h-all+mx>0)
{
for(int i=1;i<=n;i++)
{
if(i==2)continue;
ll t=cal(a[i].h,a[2].a);
if(t*a[i].a==mx && cal(a[2].h-all+mx,a[i].a)==t)
{puts("YES"); return 0;}
}
}
puts("NO");
return 0;
}
[F]
分析:
终态就是左右横跳。想想可以发现只要纵坐标能相差2,左右横跳就没问题。
一开始向上跳可以确定对方在自己上面还是下面;然后借助初始距离大于4,再上跳一次或者下跳两次就能保证纵坐标相差2。
代码如下
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int d,d2;
scanf("%d",&d);
printf("U\n"); fflush(stdout);
scanf("%d",&d2);
if(d2==d){printf("U\n"); fflush(stdout);}
else
{
printf("D\n"); fflush(stdout);
scanf("%d",&d);
printf("D\n"); fflush(stdout);
}
int f=0;
while(1)
{
scanf("%d",&d);
if(d==0||d==-1)break;
printf("%c\n",(f)?'L':'R');
fflush(stdout);
f^=1;
}
return 0;
}
[J]
分析:
第一步分析答案是怎样组成的。对于移动第 $ i $ 个文件而言,对答案的贡献是:( 之前移动的文件中 $ q_j > q_i $ 的个数 $ ) $ + $ ( $ 之前没动(之后移动)的文件中 $ p_j < p_i $ 的个数 ) $ + $ ( $ 1 $ (从移动后文件跨越到未移动文件) )。这点我也是容易想到的。
但是,最后一次不需要移动回未移动文件区域;然后我的思路就乱了。实际上这里可以先给问题加个限制条件,要求最后一次也要移动回未移动文件区域,得到做法后再考虑去掉条件。
加上限制条件后,可以把答案写出式子来了;然后经过一些化简,可以发现按二位偏序的拓扑序移动是最优的。这样去掉限制条件也变得容易了,此题就做完了。题解讲得很清楚,赞。
心得就是:在基础思路上,我还需要:加限制条件,写式子化简。