多校4:
丽洁大神的思维果然不是一般人能懂。连看个标程都费劲得死。
所以人家能IOI第一呢。
本场又被吊打,不过结果是其它队做出来的1009是个错题……
好吧来看题目。
1001,hdu4897:树链剖分!为了做这题我才学的!
CLJ的代码风格太过诡异而不能看懂,我参照题解思考了一个算法:
线段树上保存两种标记,标记1是用于操作1,当翻转某条路径时,将该路径的标记1反转,并记录区间和。这个标记是用来表示边的,因此每个边的标记保存在该边两点中更深的一个节点上。
标记2用于操作2,因为一个节点可能连了很多轻链,因此修改节点来代替修改轻链,而重链则直接修改。用一条边的两个节点的标记2是否相同来表示这条边是否被操作2反转。因此对一条路径做操作2就是,就是将路径上重链的标记2反转,并将这条重链链头链尾相邻的重链也修改(修改标记1)。
这样查询某条边是否是黑就是
轻链:a.tag1^a.tag2^b.tag2,a、b是此边的两个端点,dep[a]>dep[b]。(轻链只有两个点!)
重链:用区间求和计算路径上的tag1的和。
链头链尾要小心。
#include
#include
#include
using namespace std;
#define ls (p<<1)
#define rs (p<<1|1)
#define NN 201000
int en[NN],fi[NN],te,ne[NN],v[NN];
int tp;
int siz[NN],son[NN],top[NN],fa[NN],dep[NN],tid[NN],rank[NN];
void addedge(int a,int b){
++te;ne[te]=fi[a];fi[a]=te;v[te]=b;
++te;ne[te]=fi[b];fi[b]=te;v[te]=a;
}
void dfs1(int u,int father,int d){
siz[u]=1;
dep[u]=d;
fa[u]=father;
int e,vv;
//printf("%d\n",u);
for(e=fi[u];e!=-1;e=ne[e]){
vv=v[e];
if (vv!=father){
dfs1(vv,u,d+1);
siz[u]+=siz[vv];
if (son[u]==-1||siz[vv]>siz[son[u]]){
son[u]=vv;
}
}
}
}
void dfs2(int u,int ttop){
top[u]=ttop;
tid[u]=++tp;
rank[tid[u]]=u;
if (son[u]==-1) return;
dfs2(son[u],ttop);
int e,vv;
for(e=fi[u];e!=-1;e=ne[e]){
vv=v[e];
if (vv!=fa[u]&&vv!=son[u]){
dfs2(vv,vv);
}
}
}
struct segtree{
int l,r,tag1,tag2,rev1,rev2;
}t[NN*4];
inline void Rev(int p,int func){
if (func==1) {t[p].rev1^=1;t[p].tag1=t[p].r+1-t[p].l-t[p].tag1;}
if (func==2) {t[p].rev2^=1;t[p].tag2^=1;}
}
void lazy(int p){
if (t[p].l==t[p].r) {t[p].rev1=t[p].rev2=0;return;}
if (t[p].rev1!=0) {
t[p].rev1=0;
Rev(ls,1);
Rev(rs,1);
}
if (t[p].rev2!=0) {
t[p].rev2=0;
Rev(ls,2);
Rev(rs,2);
}
}
void build(int l,int r,int p){
t[p].l=l;t[p].r=r;
t[p].tag1=t[p].tag2=t[p].rev1=t[p].rev2=0;
if (l==r){return;}
int m=l+r>>1;
build(l,m,ls);
build(m+1,r,rs);
}
inline void update(int p){
t[p].tag1=t[ls].tag1+t[rs].tag1;
}
void rev(int l,int r,int func,int p){
if (l>r) return;
if (t[p].l==l&&t[p].r==r){
Rev(p,func);
return;
}
lazy(p);
int m=t[p].l+t[p].r>>1;
if (r<=m) rev(l,r,func,ls);
else if (l>m) rev(l,r,func,rs);
else {
rev(l,m,func,ls);
rev(m+1,r,func,rs);
}
update(p);
}
int query(int l,int r,int p){
if (l>r) return 0;
if (t[p].l==l&&t[p].r==r){
return t[p].tag1;
}
lazy(p);
int m=t[p].l+t[p].r>>1;
if (r<=m) return query(l,r,ls);
else if (l>m) return query(l,r,rs);
else {
return query(l,m,ls)+query(m+1,r,rs);
}
update(p);
}
int querytag2(int pos,int p){
if (t[p].l==t[p].r) return t[p].tag2;
lazy(p);
int m=t[p].l+t[p].r>>1;
if (pos<=m) return querytag2(pos,ls);
else return querytag2(pos,rs);
update(p);
}
void linerev(int a,int b){
while(top[a]!=top[b]){
if (dep[top[a]]dep[b]) swap(a,b);
rev(tid[a]+1,tid[b],1,1);//注意加1,最高点不用改变
}
void linerevadj(int a,int b){
while(top[a]!=top[b]){
if (dep[top[a]]dep[b]) swap(a,b);
rev(tid[a],tid[b],2,1);//这里不用加1,路径上每个点都需要更改tag2标记
int faa=fa[a];
if (faa!=0&&son[faa]==a) rev(tid[a],tid[a],1,1);
if (son[b]!=-1) rev(tid[son[b]],tid[son[b]],1,1);
}
int linequery(int a,int b){
int ret=0;
while(top[a]!=top[b]){
if (dep[top[a]]dep[b]) swap(a,b);
ret+=query(tid[a]+1,tid[b],1);
return ret;
}
int main(){
int cas,n,q,i,a,b,c;
scanf("%d",&cas);
while(cas--){
scanf("%d",&n);
memset(fi,-1,sizeof(fi));
te=0;
for(i=1;i
1002,hdu4898:这题我几乎是把标程抄了一遍。
尤其是Check()函数我就无耻地直接Copy了一下。
这种让最大值最小、最小值最大的问题常用二分。
再讲一下Check函数的思路(想了好久),一个点可以跳到它的可跳最大值之内的任意点,将不可以往后跳的点都剔除,这样所有点都是连通的,最多可以跳剩下点数a次,最小就是每次跳最大值的次数b。因此k在[b,a]之间都是可行的。
#include
#include
#include
#include
#include
using namespace std;
#define NN 4100
char s[NN];
int n,k;
int tsub;
int lcp[NN][NN];
struct Sub{
int l,r;
void init(int _l,int _r){l=_l;r=_r;}
int size() {return r-l+1;}
char charat(int x) {
if (x>r-l) return 0;
else return s[l+x];
}
}sub[NN*NN];
int Lcp(Sub a,Sub b){
return min(lcp[a.l][b.l],min(a.size(),b.size()));
}
bool operator < (Sub a,Sub b){
int m=Lcp(a,b);
return a.charat(m) nxt(n);
for (int i = 0; i < n; ++i) {
Sub tmp;
tmp.init(i,i+n-1);
int L = Lcp(tmp, M);
if (s[(i + L) % n] < M.charat(L))
nxt[i] = n;
else
nxt[i] = L;
}
for (;;) {
bool done = true;
for (int i = 0; i < nxt.size(); ++i) {
if (nxt[i] == 0) {
for (int j = 0; j < nxt.size(); ++j)
if (j != i) {
if (j < i && j + nxt[j] >= i)
--nxt[j];
else if (j > i && j + nxt[j] >= i + nxt.size())
--nxt[j];
}
nxt.erase(nxt.begin() + i);
done = false;
break;
}
}
if (done)
break;
}
if (k > nxt.size())
return false;
for (int i = 0; i < nxt.size() * 2; ++i) {
step[i] = i + nxt[i % nxt.size()];
}
for (int i = 0; i < nxt.size(); ++i) {
int need = 0, at = i;
while (at < i + nxt.size()) {
at = step[at];
++need;
}
if (need <= k)
return true;
}
return false;
}
Sub work(){
int l=1,r=tsub,m,ans=r;
int i,j,tot;
while(l>1;
Sub a=sub[m];
if (check(a)) {
if (ans>m) ans=m;
r=m-1;
}
else l=m+1;
}
return sub[ans];
}
void output(Sub a){
int i,r=a.r;
for(i=a.l;i<=a.r;++i){
printf("%c",s[i]);
}
printf("\n");
}
int main(){
int cas;
scanf("%d",&cas);
int i,j;
while(cas--){
scanf("%d%d",&n,&k);
scanf("%s",s);
for(i=0;i=0;--i){
for(j=n+n-1;j>=0;--j){
if (s[i]==s[j]) lcp[i][j]=lcp[i+1][j+1]+1;
else lcp[i][j]=0;
}
}
tsub=0;
Sub tmp;
for(i=0;in) break;
tmp.init(i,j);
sub[++tsub]=tmp;
}
}
sort(sub+1,sub+tsub+1);
tmp=work();
output(tmp);
}
return 0;
}
1006,hdu4902:线段树!
神奇诡异的证明,反正我是不会。不过看到那么多队伍都通过,就怀疑着写了一发。(居然过了什么的…)
做法看代码吧,用一个标记标记一段相同,用一个标记标记段最大值,如果2操作的值比这段的最大值大,就无视这次操作,否则就暴力往下做。由于gcd是越来越小,而且一个数取gcd最多不超过32次。想想应该操作次数应该不会太多。
#include
#include
#include
#include
using namespace std;
#define ls (p<<1)
#define rs (p<<1|1)
#define NN 101000
int val[NN],cas,n;
struct tree{
int l,r,ma,res,val;
}t[NN*4];
void lazy(int p){
if (t[p].res==0) return;
else if (t[p].res==1){
t[p].res=0;
t[p].ma=t[p].val;
if (t[p].l==t[p].r) return;
t[ls].res=1;t[ls].ma=t[ls].val=t[p].val;
t[rs].res=1;t[rs].ma=t[rs].val=t[p].val;
}
}
void build(int l,int r,int p){
t[p].l=l;t[p].r=r;
t[p].res=0;
if (l==r) {t[p].ma=t[p].val=val[l];return;}
int m=(l+r)>>1;
build(l,m,ls);
build(m+1,r,rs);
t[p].ma=max(t[ls].ma,t[rs].ma);
}
void update(int l,int r,int val,int p){
if (t[p].l==l&&t[p].r==r) {
t[p].res=1;
t[p].val=t[p].ma=val;
return;
}
lazy(p);
int m=(t[p].l+t[p].r)>>1;
if (r<=m) update(l,r,val,ls);
else if (l>m) update(l,r,val,rs);
else {
update(l,m,val,ls);
update(m+1,r,val,rs);
}
t[p].ma=max(t[ls].ma,t[rs].ma);
}
void fk(int l,int r,int val,int p){
if (t[p].l==t[p].r){
t[p].res=0;
if (t[p].val>val) t[p].val=__gcd(val,t[p].val);
return;
}
if (t[p].l==l&&t[p].r==r&&t[p].res==1) {
if (t[p].val>=val) {
t[p].val=__gcd(val,t[p].val);
}
return;
}
lazy(p);
int m=(t[p].l+t[p].r)>>1;
if (t[p].l==l&&t[p].r==r){
if (t[p].ma<=val) ;
else{
fk(l,m,val,ls);
fk(m+1,r,val,rs);
t[p].ma=max(t[ls].ma,t[rs].ma);
}
return;
}
lazy(p);
if (r<=m) fk(l,r,val,ls);
else if (l>m) fk(l,r,val,rs);
else {
fk(l,m,val,ls);
fk(m+1,r,val,rs);
}
t[p].ma=max(t[ls].ma,t[rs].ma);
}
int query(int pos,int p){
if (t[p].l==t[p].r){
return t[p].val;
}
lazy(p);
int m=(t[p].l+t[p].r)>>1;
if (pos<=m) return query(pos,ls);
else return query(pos,rs);
}
int main(){
//freopen("1006in.txt","r",stdin);
int i,a,b,c,d,q;
scanf("%d",&cas);
while(cas--){
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d",&val[i]);
}
build(1,n,1);
scanf("%d",&q);
for(i=1;i<=q;++i){
scanf("%d%d%d%d",&a,&b,&c,&d);
if (a==1){
update(b,c,d,1);
}
else if (a==2){
fk(b,c,d,1);
}
}
for(i=1;i<=n;++i){
printf("%d ",query(i,1));
}
printf("\n");
}
return 0;
}
1010,hdu4906:状态压缩DP
用dp[i][state]表示到第i个数能凑出state中有1的位的数的子序列有多少个。O(2^20*20*20)的复杂度。被卡掉。后来赵老师提醒0的状态很多,特判可以节省不少时间,再交,还是被卡。最后把memset改成for赋初值才通过。其实可以不用滚动数组,小的状态只会影响大的状态,状态从大到小dp就可以了。哎,真傻。
#include
#include
#include
using namespace std;
int mod=1000000007;
int dp[2][2200000];
int n,k,l;
int cas,tn,kl,tmpk,now,pas,i,j,o;
long long ns;
int intns;
int ans;
int main(){
//freopen("1010in.txt","r",stdin);
scanf("%d",&cas);
tn=0;
while(cas--){
scanf("%d%d%d",&n,&k,&l);
memset(dp,0,sizeof(dp));
tn=(1<k) tmpk+=l-k;
now=0;
dp[0][0]=1;
for(i=1;i<=n;++i){
pas=now;now=1-now;
for(j=0;j