吐槽
怎么一天只能发十篇啊。[虽然一天写不满十篇但是就是要槽一槽]
codeforces 557E(回文串dp)
题意
略
分析
定义dp[i][j] 表示[i,j]是否是一个半回文。
if (i+2<=j-2)dp[i][j]=dp[i+2][j-2]&(s[i]==s[j])
然后全都丢进tire树里,通过trie树上二分这一操作来得到字典序k大的串。
值得注意的是,如果有一些串可以合并起来一起加进去的话的(简单来说就是一为另一的前缀)那么就一起加会比较省时间。
code
#include<bits/stdc++.h>
using namespace std;
bool dp[5005][5005];
int n,k,num[5005];
char s[5005];
int to[20000005][2],sum[20000005],cnt[20000005],tot;
struct trie{
inline void pt(int p,int k){
for (;;){
if (cnt[p]>=k){
putchar('\n');
return;
}
k-=cnt[p];
if (to[p][0]&&sum[to[p][0]]>=k){
putchar('a');
p=to[p][0];
}
else{
if (to[p][0])k-=sum[to[p][0]];
putchar('b');
p=to[p][1];
}
}
}
inline void add(int x,int num){
int p=0;
for (int i=x;i<=n;i++){
if (!to[p][s[i]-'a']){
to[p][s[i]-'a']=++tot;
}
p=to[p][s[i]-'a'];
sum[p]+=num;
if (dp[x][i]){
num--;
cnt[p]++;
}
}
}
}T;
int main(){
// freopen("1.in","r",stdin);
scanf("%s",s+1);
n=strlen(s+1);
scanf("%d",&k);
for (int len=1;len<=n;len++){
for (int i=1;i+len-1<=n;i++){
int j=i+len-1;
if (i+2<=j-2)dp[i][j]=(dp[i+2][j-2]&(s[i]==s[j]));
else dp[i][j]=(s[i]==s[j]);
num[i]+=dp[i][j];
}
}
for (int i=1;i<=n;i++)if(num[i])T.add(i,num[i]);
T.pt(0,k);
return 0;
}
246E(启发式合并)
题意
对于树上的每一个节点都有一个颜色,给出一些询问求某个点的子树内深度距离他恰好为k的点中颜色种类数量。
分析
简单来说肯定就是离线然后瞎搞吧。
然后就自然而然的想到了启发式合并(dsu on tree[?])然后就是一些普通的技巧了。
每次清空就打时间戳。
重儿子还是要继承的。
然后就没有东西啦。
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define A first
#define B second
#define mp make_pair
#define M 100005
void read(int &x){
x=0; char c=getchar();
for (;c<48;c=getchar());
for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
map<string,int>id;
set< pii >s[M];
struct ed{
int x,nx;
}e[M];
int res[M],fa[M],ki[M];
struct query{
int id,dis;
};
vector<query>Q[M];
int ti[M],n,son[M],cnt[M],TIM=1;
int nx[M],ecnt,col[M],tot,de[M],num[M];
void add(int x,int y){
e[ecnt]=(ed){y,nx[x]};
nx[x]=ecnt++;
}
void dfs1(int f,int x){
de[x]=de[f]+1; cnt[x]=1;
for (int i=nx[x];~i;i=e[i].nx){
dfs1(x,e[i].x);
cnt[x]+=cnt[e[i].x];
if (cnt[son[x]]<cnt[e[i].x]||son[x]==0)son[x]=e[i].x;
}
}
void uni(int x,int y){
pii tmp;
set< pii >::iterator it;
for (it=s[y].begin();it!=s[y].end();++it){
tmp=*it;
if (s[x].find(tmp)==s[x].end()){
if (ti[tmp.A]!=TIM){
ti[tmp.A]=TIM; num[tmp.A]=0;
}
num[tmp.A]++;
s[x].insert(tmp);
}
}
s[y].clear();
}
void dfs2(int f,int x){
TIM++;
for (int i=nx[x];~i;i=e[i].nx)if(e[i].x!=son[x]){
dfs2(x,e[i].x);
TIM++;
}
if (son[x]){
dfs2(x,son[x]);
ki[x]=ki[son[x]];
}
else {
ki[x]=x;
}
for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=son[x]){
uni(ki[x],ki[e[i].x]);
}
s[ki[x]].insert(mp(de[x],col[x]));
if (ti[de[x]]!=TIM)num[de[x]]=0,ti[de[x]]=TIM; num[de[x]]++;
for (int i=0;i<Q[x].size();i++){
if (Q[x][i].dis<=n+5&&ti[Q[x][i].dis]==TIM)res[Q[x][i].id]=num[Q[x][i].dis];
}
}
int main(){
// freopen("1.in","r",stdin);
int x,y,dis,m;
read(n);
string ss;
memset(nx,-1,sizeof(nx));
for (int i=1;i<=n;i++){
cin>>ss; read(fa[i]);
if (id[ss])col[i]=id[ss];
else col[i]=id[ss]=++tot;
add(fa[i],i);
}
dfs1(0,0);
read(m);
for (int i=1;i<=m;i++){
read(x); read(dis);
Q[x].push_back((query){i,de[x]+dis});
}
dfs2(0,0);
for (int i=1;i<=m;i++)printf("%d\n",res[i]);
return 0;
}
631E(斜率优化dp)
题意
略…
分析
简单来说就是推一个表达式,发现是直线的形式,然后通过类似斜率优化dp的方式来得到答案。
res=sum[n]-a[i]*i+a[i]*j-sum[j]+sum[i]
res=sum[n]-a[i]*i+sum[i] +a[i]*j-sum[j]
max(a[i]*j-sum[j])
res=sum[n]-a[i]*i+a[i]*j-sum(j,i-1)
res=sum[n]-a[i]*i+sum[i] +a[i]*j-sum[j]
max(a[i]*j-sum[j])
需要考虑的是 对于i,j的位置需要分类,然后才能得到答案。
231E(仙人掌初尝试)
题意
给一个仙人掌图,求2^某个点到另外一個点的路径上环的个数。
分析
这道题目的仙人掌保证一个点只在一个环里面出现,总之还是很舒服的。
考虑把点缩到环里面,那么图就变成了一棵树。
然后就是树上最短路径了。(其实就是求个LCA就解决了)
code
#include<bits/stdc++.h>
#define pb push_back
#define mo 1000000007
using namespace std;
void read(int &x){
x=0; char c=getchar();
for (;c<48;c=getchar());
for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
#define M 500005
vector<int>E[M];
int n,num[M];
void Min(int &x,int y){
if (x>y)x=y;
}
void Max(int &x,int y){
if (x<y)x=y;
}
int col[M];
struct Tarjan{
int top,colcnt,de[M],low[M],dfn[M],tot;
bool vis[M],cut[M<<1];
void init(){
memset(nx,-1,sizeof(nx)); ecnt=0;
}
struct ed{
int x,nx;
}e[M<<1];
int nx[M],ecnt;
void add(int x,int y){
e[ecnt]=(ed){y,nx[x]};
nx[x]=ecnt++;
}
int dfs(int f,int x){
dfn[x]=low[x]=++tot;
for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=f){
if (!dfn[e[i].x]){
Min(low[x],dfs(x,e[i].x));
if (low[e[i].x]>dfn[x])cut[i]=cut[i^1]=1;
}
else{
Min(low[x],dfn[e[i].x]);
}
}
return low[x];
}
int color(int f,int x,int Col){
int cnt=1;
col[x]=Col;
for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=f&&!cut[i]&&!col[e[i].x])cnt+=color(x,e[i].x,Col);
return cnt;
}
void solve(){
top=0; colcnt=0;
dfs(1,1);
for (int i=1;i<=n;i++)if (!col[i]){
++colcnt;
num[colcnt]=color(i,i,colcnt);
if (num[colcnt]>=2)num[colcnt]=1;
else num[colcnt]=0;
}
for (int i=1;i<=n;i++){
for (int j=nx[i];~j;j=e[j].nx)if (cut[j]){
E[col[i]].pb(col[e[j].x]);
}
}
}
}tarjan;
int fa[M],dis[M],cnt[M],son[M],de[M],top[M];
void dfs1(int f,int x){
dis[x]=dis[f]+num[x];
cnt[x]=1; fa[x]=f; de[x]=de[f]+1;
for (int i=0;i<E[x].size();i++)if (E[x][i]!=f){
dfs1(x,E[x][i]);
cnt[x]+=cnt[E[x][i]];
if (cnt[E[x][i]]>cnt[son[x]])son[x]=E[x][i];
}
}
void dfs2(int f,int x){
top[x]=f;
if (son[x])dfs2(f,son[x]);
for (int i=0;i<E[x].size();i++)if (E[x][i]!=fa[x]&&E[x][i]!=son[x])dfs2(E[x][i],E[x][i]);
}
int lca(int x,int y){
for (;top[x]!=top[y];){
if (de[top[x]]>de[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
if (de[x]<de[y])return x; return y;
}
int po[M];
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
int m,x,y,q;
read(n); read(m);
tarjan.init();
for (int i=1;i<=m;i++){
read(x); read(y);
tarjan.add(x,y); tarjan.add(y,x);
}
tarjan.solve();
dfs1(1,1);
dfs2(1,1);
read(q);
po[0]=1;
for (int i=1;i<=n;i++)po[i]=(po[i-1]*2ll)%mo;
for (int i=1;i<=q;i++){
read(x); read(y);
int l=lca(col[x],col[y]);
printf("%d\n",po[dis[col[x]]+dis[col[y]]-2*dis[l]+num[l]]);
}
return 0;
}
总结
总之能想出这道题的解法还是非常神奇的。
似乎在通过资料了解的仙人掌图和这个并不大一样。
然而对于这样的一种仙人掌,可以通过tarjan求出所有的割边然后把这些边切断,之后在对所有的环染色,缩成一个点。
然后对于普通的仙人掌可以考虑看看这个
799E(PQ强搞太烦 → → 线段树万岁)
题意
有n个物品,购买物品i需要花费ci的代价。Arkady和Masha分别有喜欢的物品。
现在需要从中选m个,使得这m个物品中至少有k个Arkady喜欢的物品,k个Masha喜欢的物品。
输出满足要求的最小代价,无解输出-1。
分析
首先发现可以考虑有多少个两个人都喜欢的物品被选中,那么也就能知道只有A喜欢的至少要多少个,只有B喜欢的至少要多少个,以及剩下的所有物品中至少要选择多少个。[可以发现能包涵所有情况]
那么维护几个堆就能解决这个问题。
但是实现起来,各个堆之间的元素要丢来丢去非常麻烦,考虑换一种方式来求。
同样是枚举AB都喜欢的有x个,确定A喜欢的k-x个,确定B喜欢k-x个,那么剩下m+x-2*k在剩下的所有物品里面取。
首先对所有物品排序,建一棵线段树,维护区间内可以选择的物品数量和价值总和。
然后调出AB喜欢,A喜欢,B喜欢的东西,然后分别排序。
之后枚举AB都喜欢的物品的个数,然后贪心的选择即可,把选择了的东西从线段树中删去,最后再找线段树前m+x-2*k大个物品的数值和就好了。[细节就变少了]
467E(单调栈+找性质)
题意
对于一个序列a,要求在里面找到最长的子序列s,满足
1.序列长度=4*k
2.对于任意的(0<=i < k)
si∗4+1=si∗4+3∩si∗4+2=si∗4+4
s
i
∗
4
+
1
=
s
i
∗
4
+
3
∩
s
i
∗
4
+
2
=
s
i
∗
4
+
4
输出这个序列。
分析
一开始的想法是线段树。然而可以发现如果是连续四个相同的数字的情况就并不好解决了。
144E(找性质+线段树天下第一)
题意
略
分析
一个点最多一直往上走或者一直往左走。
那么一个点(x,y)对应的能走到对应一段连续的区间[n-x+1,y],那么其实就是能在区间中间放一个点,然后考虑最多能放入多少个点。
感觉上是传统的贪心,但是由于忘了怎么写于是用线段树写了一波。
313E(玄妙贪心)
题意
略
分析
首先把两个东西排序。然后从大到小考虑a[i],在b中找出一个b[j] 使得a[i]+b[j]
263E(略恶心的dp)
分析
可以发现所有f(x,y)是四个三角形+a[x][y]*k
然后这样的话预处理一下就出来了。
四个三角形可以通过旋转网格的方式来轻松解决,最后加在f数组里就好了。
257E(超恶心的模拟)
题意
就是如果某一个时间在上面楼层等待的人的数量+当前电梯中想要上楼的人的数量的和>=在下面楼层等待的人的数量+当前电梯中想要下楼的人数量的和
那么电梯往上走。否则电梯往下走。
特别的,如果没有人等待也没有人在电梯里,电梯不动。
分析
感觉就是非常槽心的模拟题。
首先给出一个总的框架。
首先先是四个结构体。
wup:
在楼上等待的一波人,每次询问要求最靠下的楼层是哪一层
支持添加和删除
up:
当前电梯中向上走的一波人,每次询问要求最靠下的楼层是哪一层
支持添加和删除
down:
当前电梯中向下走的一波人,每次询问要求最靠上的楼层是哪一层
支持添加和删除
wdown
在楼下等待的一波人,每次询问最靠上的楼层是哪一层
支持添加和删除
当前的时间是tim,电梯的位置是pos
然后是主要的框架。
void add(int i,int pos){
if (st[i]==pos){
if (to[i]