A. Mind Control
略
B. Irreducible Anagrams
略
C. Prefix Enlightenment
略
D. Coffee Varieties (hard version)
vp的时候智障的算错了常数,写了一个操作次数2n2k\frac{2n^2}{k}k2n2?WA了,然后不想改了(虽然区别不大)。
考虑分治,定义solve(l,r)返回一个l?rl\sim rl?r间的下标集合,使得[l,r][l,r][l,r]中每种不同权值恰好在这个下标集合中对应出现一次。
令len=r?l+1len=r-l+1len=r?l+1,这里有len=2clen=2^clen=2c,当len=klen=klen=k的时候我们可以直接kkk次操作求解(每次加入一个看是否出现过,操作完后要清空)。
对于len>klen>klen>k的,相当于要合并两个下标集合,其实只需要把两个集合并起来,再删去同时出现在两边的权值的其中一次出现即可。
一个暴力的实现是将左右下标集合各自k2\frac{k}{2}2k?个分一块,每次取出一个左边的块插入所有数字,再取出一个右边的块依次插入看有没有出现过即可(操作完后要清空)。这样单层最多要len2k\frac{len^2}{k}klen2?次操作,易知总操作次数上界为2n2k\frac{2n^2}{k}k2n2?。
考虑稍微优化一下,仍然k2\frac{k}{2}2k?个分一块,但是不急着清空。每次取出左边两个块和右边两个块,先加入左边第一个块,然后插入右边第一个块并查看是否出现过,再插入左边第二个块并查看是否出现过,再插入右边第二个块并查看是否出现过,最后再插一次左边第一个块。这样利用了只保留最后kkk个的特性,不会同时存在自身的两次插入。注意最后剩下的散块要暴力处理。
简单分析可知这样的操作次数上界为8n25+O(n)\frac{8n^2}{5}+\mathcal O(n)58n2?+O(n)的,时间复杂度为O(n2)\mathcal O(n^2)O(n2)。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3fusing namespace std;typedef long long ll;int val[1025],q[1000005];
int L=1,R=0;
int vis[1025];int n,k,sz;int query(int y) {
if (!y) {
/*L=1;R=0;memset(vis,0,sizeof(vis));return 0;*/puts("R");fflush(stdout);return 0;}else {
sz++;assert(sz<=(3*n*n)/(2*k));/*bool v=vis[val[y]];vis[val[y]]++;q[++R]=y;if (R-L+1>k) {vis[val[q[L]]]--;L++;}return v;*/printf("? %d\n",y);fflush(stdout);char str[5];scanf("%s",str);return (str[0]=='Y');}
}vector<int> solve(int l,int r) {
if (r-l+1==k) {
vector<int> vt;for(int i=l;i<=r;i++)if (!query(i)) vt.push_back(i);query(0);return vt;}int m=((l+r)>>1);vector<int> a=solve(l,m);vector<int> b=solve(m+1,r);bool cur[1025];memset(cur,0,sizeof(cur));for(int i=0;i<a.size();i+=k)for(int j=0;j<b.size();j+=k) {
if (i+k-1>=a.size()||j+k-1>=b.size()) {
for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);for(int l=j;l<j+(k>>1)&&l<b.size();l++)if (query(b[l])) cur[b[l]]=1;query(0);for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);for(int l=j+(k>>1);l<j+k&&l<b.size();l++)if (query(b[l])) cur[b[l]]=1;query(0);for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);for(int l=j;l<j+(k>>1)&&l<b.size();l++)if (query(b[l])) cur[b[l]]=1;query(0);for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);for(int l=j+(k>>1);l<j+k&&l<b.size();l++)if (query(b[l])) cur[b[l]]=1;query(0);}else {
for(int l=i;l<i+(k>>1);l++) query(a[l]);for(int l=j;l<j+(k>>1);l++)if (query(b[l])) cur[b[l]]=1;for(int l=i+(k>>1);l<i+k;l++)if (query(a[l])) cur[a[l]]=1;for(int l=j+(k>>1);l<j+k;l++)if (query(b[l])) cur[b[l]]=1;for(int l=i;l<i+(k>>1);l++)if (query(a[l])) cur[a[l]]=1;query(0);}}vector<int> vt;for(int i=0;i<a.size();i++)if (!cur[a[i]]) vt.push_back(a[i]);for(int i=0;i<b.size();i++)if (!cur[b[i]]) vt.push_back(b[i]);return vt;
}int main() {
scanf("%d%d",&n,&k);//for(int i=1;i<=n;i++) scanf("%d",&val[i]);if (k==1) {
int s=0;for(int i=1;i<=n;i++) {
bool ok=1;for(int j=i+1;j<=n;j++) {
query(i);if (query(j)) ok=0;query(0);if (!ok) break;}s+=ok;}printf("! %d\n",s);fflush(stdout);return 0;} vector<int> ans=solve(1,n);printf("! %d\n",(int)ans.size());fflush(stdout);return 0;
}
/* 4 2 1 4 1 3 */
E. Cartesian Tree
答案等价于计算点对(i,j)(i,j)(i,j)的数目,使得点jjj在点iii的子树中。根据笛卡尔树的性质,jjj在iii的子树中当且仅当在序列上iii与jjj所在位置之间最大值为iii。
考虑分治,对于一个最终序列中的区间[l,r][l,r][l,r],求出[l,mid][l,mid][l,mid]与[mid+1,r][mid+1,r][mid+1,r]之间的贡献。根据上面的讨论,这里显然只需要关注在区间中的元素,且可以发现一个右边的点aja_jaj?会在左边的点aia_iai?的子树中当且仅当ai=max?k=imidaka_i=\max_{k=i}^{mid}a_kai?=maxk=imid?ak?且ai>max?k=mid+1jaka_i>\max_{k=mid+1}^{j}a_kai?>maxk=mid+1j?ak?,反之亦然。那么可以发现一个左边的元素aia_iai?会与右边的点有贡献当且仅当它是[i,mid][i,mid][i,mid]中的最大值,且有贡献的jjj是右边的前缀。
考虑按插入时间从小到大维护对答案的贡献。维护两个从中间向两侧的元素单调栈(即保存左边的后缀最大值和右边的前缀最大值),同时用树状数组维护当前所有元素,支持查询区间元素个数。不放设现在在左边插入了一个最大值axa_xax?,那么我们会从左边的单调栈中弹出所有在xxx左侧的后缀最大值,并减去它们在右边的子树大小和,这可以在右边的单调栈中二分出它们在右边的贡献前缀,并用树状数组查询个数完成。同时也要减去右边的前缀最大值对左边的贡献,这部分可以考虑反过来统计,由于aia_iai?在右边的祖先数目只跟max?j=imidaj\max_{j=i}^{mid}a_jmaxj=imid?aj?有关,那么可以对刚刚弹出的后缀最大值划分出的每一段分别计算,用树状数组算出左边的个数,在右边单调栈二分即可。最后还要记得加上xxx在右边的子树大小,也即右边当前元素个数。
时间复杂度为O(nlog?2n)\mathcal O(n\log^2n)O(nlog2n)。
#include <bits/stdc++.h>
#define lowbit(x) (x&-x)using namespace std;typedef long long ll;int sumv[200005];void add(int x,int n,int num) {
for(;x<=n;x+=lowbit(x)) sumv[x]+=num;
}int sum(int x) {
int s=0;for(;x;x-=lowbit(x)) s+=sumv[x];return s;
}int sum(int l,int r) {
return sum(r)-sum(l-1);
}int num[200005],pos[200005],n;
ll ans[200005];int stl1[200005],stl2[200005];
int str1[200005],str2[200005];
int cur[200005];void solve(int l,int r) {
if (l==r) return;int m=((l+r)>>1);solve(l,m);solve(m+1,r);int top1=0,top2=0,sz=0;for(int i=l;i<=r;i++) cur[++sz]=num[i];sort(cur+1,cur+sz+1);int lsz=0,rsz=0;for(int i=1;i<=sz;i++) {
int x=cur[i],y=pos[x];if (y<=m) {
lsz++;int rx=upper_bound(stl2+1,stl2+top1+1,-y)-stl2-1;if (rsz) {
for(int j=rx+1;j<=top1;j++) {
int t=upper_bound(str1+1,str1+top2+1,stl1[j])-str1;if (t>top2) ans[x]-=sum(m+1,r);else if (t) ans[x]-=sum(m+1,str2[t]-1); if (t<=top2) ans[x]-=(ll)sum(((j==top1)?l:-stl2[j+1]+1),-stl2[j])*(top2-t+1);}if (rx) {
int t=upper_bound(str1+1,str1+top2+1,stl1[rx])-str1;if (t<=top2) ans[x]-=(ll)sum(((rx==top1)?l:-stl2[rx+1]+1),y)*(top2-t+1);}ans[x]+=rsz;}top1=rx+1;stl1[top1]=x;stl2[top1]=-y;}else {
rsz++;int rx=upper_bound(str2+1,str2+top2+1,y)-str2-1;if (lsz) {
for(int j=rx+1;j<=top2;j++) {
int t=upper_bound(stl1+1,stl1+top1+1,str1[j])-stl1;if (t>top1) ans[x]-=sum(l,m);else if (t) ans[x]-=sum(-stl2[t]+1,m);if (t<=top1) ans[x]-=(ll)sum(str2[j],((j==top2)?r:str2[j+1]-1))*(top1-t+1);}if (rx) {
int t=upper_bound(stl1+1,stl1+top1+1,str1[rx])-stl1;if (t<=top1) ans[x]-=(ll)sum(y,((rx==top2)?r:str2[rx+1]-1))*(top1-t+1);}ans[x]+=lsz;}top2=rx+1;str1[top2]=x;str2[top2]=y;}add(y,n,1);}for(int i=l;i<=r;i++) add(i,n,-1);
}int main() {
scanf("%d",&n);for(int i=1;i<=n;i++) {
scanf("%d",&num[i]);pos[num[i]]=i;}solve(1,n);for(int i=1;i<=n;i++) ans[i]+=ans[i-1];for(int i=1;i<=n;i++) printf("%lld\n",ans[i]+i);return 0;
}
F. Making Shapes
首先可以发现答案相当于统计所有可表示出的横纵坐标差≤m\leq m≤m的凸多边形数目。由于没有平行向量,因此只需要考虑每种向量所用的数目。那么可以发现,这等价于统计这样的集合(c1,c2,?,cn)(c_1,c_2,\cdots,c_n)(c1?,c2?,?,cn?)的数目(ci≥0c_i\geq 0ci?≥0且cic_ici?不全为000),使得:
{∑xi>0cixi=?∑xi<0cixi∑yi>0ciyi=?∑yi<0ciyi∑xi>0cixi≤m∑yi>0ciyi≤m\begin{cases} \sum_{x_i>0}c_ix_i &= -\sum_{x_i<0}c_ix_i\\ \sum_{y_i>0}c_iy_i &= -\sum_{y_i<0}c_iy_i\\ \sum_{x_i>0}c_ix_i &\leq m \\ \sum_{y_i>0}c_iy_i &\leq m \\ \end{cases} ??????????∑xi?>0?ci?xi?∑yi?>0?ci?yi?∑xi?>0?ci?xi?∑yi?>0?ci?yi??=?∑xi?<0?ci?xi?=?∑yi?<0?ci?yi?≤m≤m?
原因是这样的每个集合显然能唯一对应一个凸多边形(按方位角排序后依次加入),且不同集合对应的两两不同。
那么转化为一个计数问题,考虑按位DP。令444个方向的长度和分别为sux,sdx,suy,sdysux,sdx,suy,sdysux,sdx,suy,sdy,那么令F[i][ux][dx][uy][dy][u][v]F[i][ux][dx][uy][dy][u][v]F[i][ux][dx][uy][dy][u][v]表示考虑了每种向量数目ccc的最后iii位,且满足suxsuxsux与sdxsdxsdx后iii位相同,suysuysuy与sdysdysdy后iii位相同,sux,sdx,suy,sdysux,sdx,suy,sdysux,sdx,suy,sdy分别向前的进位为ux,dx,uy,dyux,dx,uy,dyux,dx,uy,dy,同时uuu与vvv分别表示了suxsuxsux和suysuysuy的后iii位是否不超过mmm的后iii位。令max?(∣xi∣,∣yi∣)=w\max(|x_i|,|y_i|)=wmax(∣xi?∣,∣yi?∣)=w,题目保证w≤4w\leq 4w≤4,那么容易证明任意时刻均有ux,dx,uy,dy≤nwux,dx,uy,dy\leq nwux,dx,uy,dy≤nw。
转移有两种方式,一种是直接O(2n)\mathcal O(2^n)O(2n)枚举所有ccc的第iii位转移,一种是依次DP每种ccc的第iii位。理论复杂度上第二种更优秀,但此时ux,dx,uy,dyux,dx,uy,dyux,dx,uy,dy的上界会翻倍,实际效率可能比第一种慢近百倍。
时间复杂度为O(2nn4w4log?m)\mathcal O(2^nn^4w^4\log m)O(2nn4w4logm)或O(n5w4log?m)\mathcal O(n^5w^4\log m)O(n5w4logm)。这里的代码是第二种的。
#include <bits/stdc++.h>
#define MOD 998244353using namespace std;typedef long long ll;inline void add(int &x,int y) {
((x+=y)>=MOD)?x-=MOD:0;
}int f[2][41][41][41][41][4];
int a[10],b[10];int main() {
int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);int cur=0,up=(n<<3);f[cur][0][0][0][0][0]=1;for(int i=0;i<=30;i++) {
for(int j=1;j<=n;j++) {
int ux=((a[j]>0)?a[j]:0),dx=((a[j]<0)?-a[j]:0);int uy=((b[j]>0)?b[j]:0),dy=((b[j]<0)?-b[j]:0);cur^=1;memcpy(f[cur],f[cur^1],sizeof(f[cur]));for(int t1=0;t1<=up;t1++)for(int t2=0;t2<=up;t2++)for(int t3=0;t3<=up;t3++)for(int t4=0;t4<=up;t4++)for(int t5=0;t5<4;t5++)if (f[cur^1][t1][t2][t3][t4][t5]) add(f[cur][t1+ux][t2+dx][t3+uy][t4+dy][t5],f[cur^1][t1][t2][t3][t4][t5]);}bool v=((m>>i)&1);cur^=1;memset(f[cur],0,sizeof(f[cur]));for(int t1=0;t1<=up;t1++)for(int t2=0;t2<=up;t2++)for(int t3=0;t3<=up;t3++)for(int t4=0;t4<=up;t4++) {
if (((t1^t2)&1)||((t3^t4)&1)) continue;for(int t5=0;t5<4;t5++)if (f[cur^1][t1][t2][t3][t4][t5]) {
if (!v) add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5|(t1&1)|((t3&1)<<1)],f[cur^1][t1][t2][t3][t4][t5]);else add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5&((t1&1)^((t3&1)<<1))],f[cur^1][t1][t2][t3][t4][t5]);}}}printf("%d\n",(f[cur][0][0][0][0][0]-1+MOD)%MOD);return 0;
}
/* 5 1000000000 -2 4 2 -3 0 -4 2 4 -1 -3 */