题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6592
题意:给出一个数组 a,找出最长的单峰子序列,单峰子序列的定义是先严格单调递增,再严格单调递减(也可以全单调递增或全单调递减)。然后输出长度最长的单峰子序列对应原数组的下标序列,分别输出字典序最小的和最大的。
思路:找最长的长度应该不难,只需要对正反各求一次LIS然后枚举一遍就可以找到了。难点在于如何输出。对于字典序最小的肯定是要找到第一个峰,也就是第一个满足最长单峰子序列的下标,然后左边求字典序最小的单增子序列,右边求字典序最小的单减子序列(反过来就是字典序最大的单增子序列)。所以其实问题转化为:给定一段区间,找到求字典序最小的单增子序列以及字典序最大的单增子序列的方法。
在求LIS时,我们可以记录 a 中每个值应该放到的位置 p,那么求长度为 x 的单增子序列时,对于:
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=3e5+15,inf=1e9+5;
int a[maxn],f[maxn],ff[maxn],b[maxn],g[maxn],s[maxn],ss[maxn],n;
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d",&n))
{
REP(i,1,n) a[i]=b[n-i+1]=read();
REP(i,0,n+2) g[i]=inf;
REP(i,1,n)
{
ss[i]=lower_bound(g,g+n,b[i])-g+1;
*lower_bound(g,g+n,b[i])=b[i];
s[i]=lower_bound(g,g+n,inf)-g;
}
REP(i,1,n) f[i]=s[n-i+1],ff[i]=ss[n-i+1];
REP(i,0,n+2) g[i]=inf;
REP(i,1,n)
{
ss[i]=lower_bound(g,g+n,a[i])-g+1;
*lower_bound(g,g+n,a[i])=a[i];
s[i]=lower_bound(g,g+n,inf)-g;
}
s[0]=0; s[n+1]=s[n]; f[0]=f[1]; f[n+1]=0;
int maxl=0;
REP(i,1,n+1) maxl=max(maxl,f[i]+s[i-1]);
REP(i,0,n+2) g[i]=0;
//cout<
//min
int k=0,l,r,p,tot=0;
REP(i,1,n+1) if(f[i]+s[i-1]==maxl) {k=i; l=s[i-1]; r=f[i]; break;}
REP_(i,k-1,1)
{
p=ss[i];
if(p==l) g[l]=i;
else if(a[i]<a[g[p+1]]) g[p]=i;
}
REP(i,1,l) {if(tot) putchar(' '); printf("%d",g[i]); tot++;}
p=k;
REP_(i,r,1)
{
while(p<n && ff[p]!=i) p++;
if(tot) putchar(' ');
printf("%d",p);
tot++;
}
puts("");
//max
tot=0;
REP(i,1,n+1) if(f[i]+s[i-1]==maxl) {k=i; l=s[i-1]; r=f[i];}
p=k-1;
REP_(i,l,1)
{
while(p>1 && ss[p]!=i) p--;
g[i]=p;
}
REP(i,1,l) {if(tot) putchar(' '); printf("%d",g[i]); tot++;}
REP(i,k,n)
{
p=ff[i];
if(p==r) g[r]=i;
else if(a[i]<a[g[p+1]]) g[p]=i;
}
REP_(i,r,1) {if(tot) putchar(' '); printf("%d",g[i]); tot++;}
puts("");
}
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6595
题意:对于一个数组 array,有一个 SUBSEQUENCE(array) 操作,为同等概率取一个子序列。然后有一个 CNTINVERSIONPAIRS(array) 操作,计算某个数组逆序对的个数。然后有一个 CALCULATE(array) 函数,它返回 CNTINVERSIONPAIRS(array) + CALCULATE(SUBSEQUENCE(array)) 。现在给出一个正整数 N,从 [1, N] 随机选取一个 n 进行随机全排列,然后计算 X=CALCULATE(这个全排列),求 X 的期望值取模 998244353。
思路:假设长度为 n 的全排列的所有可能的逆序对总数为 p(n),那么存在 p n = n p n − 1 + ( n − 1 ) ! ∑ i = 1 n − 1 i p_n=np_{n-1}+(n-1)!\sum\limits_{i=1}^{n-1}i pn=npn−1+(n−1)!i=1∑n−1i ,其意义是,对于长度为 n 的全排列,不论第一个数是多少,右边肯定组成了 p n − 1 p_{n-1} pn−1 个逆序对(这是左边部分),而且当第一个为 1 时,不与右边组成逆序对;当第一个为 2 时,与右边组成 (n-1)! 个逆序对……(这是右边部分)。然后对数组 p 操作 p i = p i n ! p_i=\frac{p_i}{n!} pi=n!pi ,现在的 p 数组的意义就是,p(n) 表示长度为 n 的全排列的逆序对个数期望。
然后设 f(n) 表示长度为 n 的全排列的 CALCULATE 的期望,有 f ( n ) = p ( n ) + ∑ i = 0 n C n i 2 n f ( i ) f(n)=p(n)+\sum\limits_{i=0}^{n}\frac{C_n^i}{2^n}f(i) f(n)=p(n)+i=0∑n2nCnif(i) ,左边部分就是 CNTINVERSIONPAIRS(array) 的值,右边部分表示随机取一个子序列的值,然后注意右边也有一个 f(n),移项计算之后就可以得到 f(n) 。然后最后答案 a n s [ N ] = 1 N ∑ n = 1 N f ( n ) ans[N]=\frac{1}{N}\sum\limits_{n=1}^{N}f(n) ans[N]=N1n=1∑Nf(n) 。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const LL M=998244353;
const int maxn=3005,N=3000;
LL jie[maxn],jie_[maxn],p[maxn],ans[maxn];
LL ksm(LL x,LL n)
{
LL ret=1;
while(n)
{
if(n&1) ret=ret*x%M;
x=x*x%M;
n>>=1;
}
return ret;
}
LL C(int n,int m) {return jie[n]*jie_[m]%M*jie_[n-m]%M;}
int main()
{
//freopen("input.txt","r",stdin);
jie[0]=1;
REP(i,1,N) jie[i]=jie[i-1]*i%M;
REP(i,0,N) jie_[i]=ksm(jie[i],M-2);
p[1]=0;
REP(i,2,N) p[i]=(i*p[i-1]+jie[i-1]*i*(i-1)/2)%M;
REP(i,1,N) p[i]=p[i]*jie_[i]%M;
REP(i,1,N)
{
LL x=0;
REP(j,1,i-1) x=(x+C(i,j)*ans[j])%M;
x=x*ksm(ksm(2,i),M-2)%M;
x=(p[i]+x)%M;
ans[i]=x*ksm(2,i)%M*ksm((ksm(2,i)-1+M*2)%M,M-2)%M;
}
REP(i,1,N) ans[i]+=ans[i-1];
REP(i,1,N) ans[i]=ans[i]%M*ksm(i,M-2)%M;
int n;
while(~scanf("%d",&n))
printf("%lld\n",ans[n]);
return 0;
}
题目地址:
题意:
思路:
代码:
题目地址:
题意:
思路:
代码:
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6598
题意:有 n 个士兵,每个士兵可以选择称为魔法师或者战士。然后有 m 对组合加成,对于第 i 对组合加成,如果这两个士兵都是战士,那么加成 a;如果都是魔法师,那么加成 c;如果一个战士一个魔法师,加成 a/4+c/3 。现在可以给每个士兵随意分配成为什么,问最大的加成是多少?
思路:这是一个很经典的二元组最小割问题。详见:这里
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=505;
const LL inf=1e16;
struct edge {int to; LL cap,rev; int is_rev;};
vector<edge> G[maxn];
LL dis[maxn],x[maxn],y[maxn];
int book[maxn],n,m;
void add_edge(int from,int to,LL cap)
{
G[from].push_back((edge){to,cap,(int)G[to].size(),0});
G[to].push_back((edge){from,0,(int)G[from].size()-1,1});
}
void BFS(int s)
{
mem(dis,-1); dis[s]=0;
queue<int> que; que.push(s);
while(!que.empty())
{
int v=que.front();que.pop();
REP(i,0,G[v].size()-1)
{
edge e=G[v][i];
if(dis[e.to]<0 && e.cap) dis[e.to]=dis[v]+1,que.push(e.to);
}
}
}
LL dfs(int s,int t,LL flow)
{
if(s==t) return flow;
for(int &i=book[s];i<(int)G[s].size();i++)
{
edge &e=G[s][i];
if(e.cap && dis[s]<dis[e.to])
{
LL flow2=dfs(e.to,t,min(flow,e.cap));
if(!flow2) continue;
e.cap-=flow2;
G[e.to][e.rev].cap+=flow2;
return flow2;
}
}
return 0;
}
LL max_flow(int s,int t)
{
LL flow=0,flow2;
while(1)
{
BFS(s);
if(dis[t]<0) return flow;
mem(book,0);
while((flow2=dfs(s,t,inf))>0) flow+=flow2;
}
}
void init()
{
REP(i,0,n+2) G[i].clear();
REP(i,0,n+2) x[i]=y[i]=0;
}
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d%d",&n,&m))
{
init();
LL ans=0;
while(m--)
{
int u=read(),v=read(),a=read(),b=read(),c=read();
ans+=a+c;
add_edge(u,v,3*a+2*c);
add_edge(v,u,3*a+2*c);
x[u]+=6*c; x[v]+=6*c;
y[u]+=6*a; y[v]+=6*a;
}
REP(i,1,n) add_edge(n+1,i,x[i]),add_edge(i,n+2,y[i]);
printf("%lld\n",ans-max_flow(n+1,n+2)/12);
}
return 0;
}
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6599
题意:给定一个长度为 n 的字符串 s,对于每个 i ∈ [ 1 , ∣ s ∣ ] i\in [1,|s|] i∈[1,∣s∣] ,输出有多少个长度为 i 的子串满足,这个子串是一个回文串,且这个子串的半前缀(长度为 i + 1 2 \frac{i+1}2 2i+1 的前缀)也是回文串。
思路:如果没有第二个条件限制,那么直接回文树就可以得到答案,如果有第二个限制呢?注意到回文树的后缀连接指向的是当前回文串的最长回文后缀,所以如果我们把 s 反向然后建回文树(反向不影响回文性质),那么一个结点(回文串)合法当且仅当它可以通过后缀连接跳转到一个长度为它一半的结点。根据这个性质统计答案就好了。
但是直接跳转的话复杂度不对,注意到后缀连接组成一棵树,我们根据后缀连接建树,然后从根结点 dfs,并且标记当前 dfs 路径上的结点的长度,对于某个结点,只需检查它的长度的一半是否被标记就行了。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=3e5+5;
char s[maxn];
int ans[maxn],vis[maxn];
VI G[maxn];
struct palindromic_tree
{
int (*t)[26],*link,*cnt,*len,*s,last,n,p;
palindromic_tree(int nn)
{
#define H(a) a=new int[nn+5]()
t=new int[nn+5][26]();
H(link); H(cnt); H(len); H(s);
last=n=p=0;
}
int new_node(int L) {len[p]=L; return p++;}
int get_suffix(int x) {while(s[n-len[x]-1]!=s[n]) x=link[x]; return x;}
void extend(int c)
{
s[++n]=c;
int u=get_suffix(last);
if(!t[u][c])
{
int v=new_node(len[u]+2);
link[v]=t[get_suffix(link[u])][c];
t[u][c]=v;
}
last=t[u][c]; cnt[last]++;
}
void build(char *ss)
{
s[0]=-1, link[0]=1;
new_node(0); new_node(-1);
for(int i=0;ss[i];i++) extend(ss[i]-'a');
REP_(i,p-1,0) cnt[link[i]]+=cnt[i];
}
void del()
{
#define D(x) delete[] x
D(t); D(link); D(cnt); D(len); D(s);
}
void dfs(int u)
{
if(len[u]>0)
{
vis[len[u]]=1;
if(vis[(len[u]+1)/2]) ans[len[u]]+=cnt[u];
}
for(int v:G[u]) dfs(v);
if(len[u]>0) vis[len[u]]=0;
}
void cal()
{
for(int i=1;i<p;i++) G[link[i]].pb(i);
dfs(0);
}
};
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%s",s))
{
int n=strlen(s);
palindromic_tree t(n);
reverse(s,s+n);
t.build(s);
REP(i,0,n) ans[i]=0,G[i].clear(),vis[i]=0;
t.cal();
printf("%d",ans[1]);
REP(i,2,n) printf(" %d",ans[i]);
puts("");
t.del();
}
return 0;
}
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6600
题意: 有一个数 x( x ≤ 2 n − 1 x\le 2^n-1 x≤2n−1),你可以一次性询问若干个 y i y_i yi ,对于每个 y i y_i yi ,系统会告诉你 x & y i x\&y_i x&yi 是否等于 y i y_i yi 。现在你需要保证询问的个数最少,并且输出在询问个数最少的前提下,有多少种询问方式(取模 1e6+5)。多组数据。
思路:最优的询问策略肯定是 1, 2, 4, 8, … 这样每个可以确定一位,所以询问方式就是 n! 种。因为 n 超过 1e6+5 之后取模一定是 0,所以预先计算出前面的就行了。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int M=1e6+3;
int ans[M+5];
int main()
{
//freopen("input.txt","r",stdin);
ans[1]=1;
REP(i,2,M-1) ans[i]=1ll*ans[i-1]*i%M;
int n;
while(~scanf("%d",&n))
{
if(n>=M) puts("0");
else printf("%d\n",ans[n]);
}
return 0;
}
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6601
题意:有 n 根木条长度为 a[i] 。有 Q 组询问,每一组询问给出两个数 l 和 r,你需要回答在第 l 根到第 r 根木条之间,能组成三角形的最长周长是多少。
思路:主席树。对于某个区间,先找最长的 3 根的,如果不行就找第二长的 3 根,直到能组成三角形为止。这里的复杂度是对的,因为由斐波那契数列的性质,一个长度大于 50 左右(我也没算过,大概吧)的区间一定存在可以组成三角形的三根,因为如果不存在,那么对这个区间长度排序,且都存在 a i + a i + 1 < a i + 2 a_i + a_{i+1} < a_{i+2} ai+ai+1<ai+2 ,那么最大那个就会超过数据限制。多组数据。
顺便提一下,按照我这种写法很容易MLE,所以每次要把 new 分配的空间 delete 掉,但是这样我就不用考虑清零问题了。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
template<class T>
struct president_tree
{
T *a,*aa;
int n,len,tot,*rt,*L,*R,*t;
#define mid ((l+r)>>1)
#define ID(x) (lower_bound(aa+1,aa+len+1,x)-aa)
president_tree(int n,T *origin)
{
this->n=n; len=tot=0;
a=new T[n+5](); aa=new T[n+5](); t=new int[n*30]();
rt=new int[n*30](); L=new int[n*30](); R=new int[n*30]();
REP(i,1,n) a[i]=aa[i]=origin[i];
sort(aa+1,aa+n+1);
len=unique(aa+1,aa+n+1)-aa-1;
rt[0]=build_(1,n);
REP(i,1,n) rt[i]=update_(rt[i-1],1,n,ID(a[i]));
}
int build_(int l,int r)
{
int k=++tot;
if(l>=r) return k;
L[k]=build_(l,mid); R[k]=build_(mid+1,r);
return k;
}
int update_(int k,int l,int r,int loc)
{
int now=++tot;
L[now]=L[k]; R[now]=R[k]; t[now]=t[k];
if(l==r) {t[now]++; return now;}
if(loc<=mid) L[now]=update_(L[k],l,mid,loc);
else R[now]=update_(R[k],mid+1,r,loc);
t[now]=t[L[now]]+t[R[now]];
return now;
}
T query_(int l,int r,int ll,int rr,int rk)
{
if(l==r) return aa[l];
int x=t[L[rr]]-t[L[ll]];
if(x>=rk) return query_(l,mid,L[ll],L[rr],rk);
else return query_(mid+1,r,R[ll],R[rr],rk-x);
}
T query(int l,int r,int rk) {return query_(1,n,rt[l-1],rt[r],rk);}
void del()
{
#define d(a) delete[] a
d(a); d(aa); d(rt); d(L); d(R); d(t);
}
};
const int maxn=2e5+5;
int a[maxn];
int main()
{
//freopen("input.txt","r",stdin);
int n,q;
while(~scanf("%d%d",&n,&q))
{
REP(i,1,n) a[i]=read();
president_tree<int> t(n,a);
while(q--)
{
int l=read(),r=read(),flag=0;
if(r-l+1<3) {puts("-1"); continue;}
REP_(k,r-l+1,3)
{
int x=t.query(l,r,k);
int y=t.query(l,r,k-1);
int z=t.query(l,r,k-2);
if(y+z>x) {flag=1; printf("%lld\n",(LL)x+y+z); break;}
}
if(!flag) puts("-1");
}
t.del();
}
return 0;
}
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6602
题意:给出 C,K 和长度为 N 的数组 a( 1 ≤ a i ≤ C 1\le a_i\le C 1≤ai≤C),定义一个连续的子序列 a [ l , r ] a[l,r] a[l,r] 为好的,当且仅当以下条件成立:
∀ x ∈ [ 1 , C ] , ( ∑ i = l r [ a i = x ] = 0 ) o r ( ∑ i = l r [ a i = x ] ≥ K ) \forall x\in[1,C], \ (\sum\limits_{i=l}^r[a_i=x]=0) \ or \ (\sum\limits_{i=l}^r[a_i=x]\ge K) ∀x∈[1,C], (i=l∑r[ai=x]=0) or (i=l∑r[ai=x]≥K)
也就是说,在 a [ l , r ] a[l,r] a[l,r] 中出现的元素,次数一定要大于等于 K 。
求最长的好子序列的长度。多组数据。
思路:对于某个 x,把等于 x 的下标从小到大存起来,假设为 p 数组,那么对于某个 p[i],只有 p[i-K] 以及以前的坐标才对 x 这个数成立。所以遍历右端点 r,用线段树维护 [1, r] 中每个点成立的数的个数。遍历到 r 时,r 这个位置成立个数初始化为 C-1,然后把 a[r] 上一个位置和这一个位置之间的位置全部 -1(表示 a[r] 对这个区间不成立,而只减这个区间是因为避免重复),然后如果存在的话再把上 K 个位置和上 K-1 个位置的区间的所有位置 +1(表示这个 a[r] 对这个区间成立,这里也要定义一个比如左开右闭,避免重复)。然后对于每个 r 找到等于 C 的位置的最小值,更新答案即可。
代码:
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
int n,C,K,a[maxn],cur[maxn];
VI p[maxn];
template <class T>
struct segment_tree_max
{
#define chl (k<<1)
#define chr (k<<1|1)
#define mid ((l+r)>>1)
T *t,*tag;
int n;
segment_tree_max(int n) {t=new T[n<<2](); tag=new T[n<<2](); this->n=n;}
void push_up(int k) {t[k]=max(t[chl],t[chr]);}
void push_down(int k,int l,int r)
{
if(!tag[k]) return;
t[chl]+=tag[k]; t[chr]+=tag[k];
tag[chl]+=tag[k]; tag[chr]+=tag[k]; tag[k]=0;
}
void update_add(int k,int l,int r,int ll,int rr,T x)
{
if(l>rr || ll>r) return;
if(l>=ll && r<=rr) {t[k]+=x; tag[k]+=x; return;}
push_down(k,l,r);
update_add(chl,l,mid,ll,rr,x);
update_add(chr,mid+1,r,ll,rr,x);
push_up(k);
}
void update_add(int ll,int rr,T x) {update_add(1,1,n,ll,rr,x);}
int query(int k,int l,int r)
{
if(l>=r) return t[k]==C?l:-1;
push_down(k,l,r);
if(t[chl]<C && t[chr]<C) return -1;
if(t[chl]==C) return query(chl,l,mid);
if(t[chr]==C) return query(chr,mid+1,r);
}
int query() {return query(1,1,n);}
};
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d%d%d",&n,&C,&K))
{
REP(i,1,C) p[i].clear(),p[i].pb(0),cur[i]=0;
REP(i,1,n) a[i]=read(),p[a[i]].pb(i);
segment_tree_max<int> t(n);
int ans=0;
REP(i,1,n)
{
t.update_add(i,i,C-1);
int c=++cur[a[i]];
if(p[a[i]][c-1]+1<=p[a[i]][c]-1)
t.update_add(p[a[i]][c-1]+1,p[a[i]][c]-1,-1);
if(c>=K)
{
int r=p[a[i]][c-K+1],l=p[a[i]][c-K]+1;
if(l<=r) t.update_add(l,r,1);
}
int x=t.query();
if(x>=0) ans=max(ans,i-x+1);
}
printf("%d\n",ans);
}
return 0;
}