noip29
T1
以下的LIS代指最长不降子序列。
考场看到取模,便想到了之前写过的Medain,取模操作让序列分布均匀,对应到本题上,既然是求LIS,那它应该是有循环节的,后来打表证实确实是有。
然后,我码了个BIT优化LIS。觉得应该能拿30pts,然后就傻逼的跳了,其实正解已经想的差不多了,但是本着先把暴力分都拿到的原则,就跳了 我是sb
考场想法:
既然有循环节,那么就只需要把整个序列拆成三部分分别为A,B,C,其中A为包含一个循环节的最短的那一段序列,B全是循环的,C是最后那一段不完全的循环节,如果有的话。
那么就只需要对A求一下LIS,并记录最优答案是从那个位置转移过来的,多个的话全都记,然后看B中有多少个循环节,就对答案产生多少贡献,最后再对记录下来的求一遍LIS,然后对应到C上,看能拿多少。
看似正确,然而我没细想,假了,或者说我的打法假了,比如下面这组数据:
12
134 33 107 69 41
生成序列为:
134 32 16 20 34 35 0 28 32 16 20 34
很显然,LIS长度为5,但我的会输出4,因为它在A中求了个4,选的是 16,20,34,35,导致在C中选不了,但显然,最优应该是在A中选16,20,28,然后在C中32,34,LIS长度为5。
然而考试的时候也没调出来,30pts的也没交上,只能说是活该吧
正解:
就是找循环节,然后一顿乱搞,把循环节接150遍,其他的贡献都是1。
我的那份调了半个下午没调出来,所以换了做法
pdf上的矩阵快速幂不会,也懒的粘了,就这样吧。
Code
#include<cstdio>
#define MAX 1000010
#define re register
namespace OMA
{
long long n,ans;
int t,a,b,c,d,m,len,xam;
int tmp[MAX],pos[MAX];
inline int max(int a,int b)
{ return a>b?a:b; }
class BIT
{
private:
int tree[MAX];
inline int lowbit(int x)
{ return x&-x; }
public:
inline void insert(int x,int lis)
{
for(re int i=x; i<=xam; i+=lowbit(i))
{ tree[i] = max(tree[i],lis); }
}
inline int query(int x)
{
int res = 0;
for(re int i=x; i; i-=lowbit(i))
{ res = max(res,tree[i]); }
return res;
}
}BIT;
signed main()
{
scanf("%lld%d%d%d%d%d",&n,&t,&a,&b,&c,&d);
if(n==1)
{ printf("1\n"); return 0; }
tmp[1] = t;
for(re int i=2; i<=n; i++)
{
xam = max(xam,tmp[i] = (a*tmp[i-1]*tmp[i-1]+b*tmp[i-1]+c)%d);
if(pos[tmp[i]])
{ len = i-pos[tmp[i]],m = i-1; break ; }
pos[tmp[i]] = i;
}
xam += 1;
ans = (n-m)/len-150,n = (n-m)%len+len*150+m;
for(re int i=m+1; i<=n; i++)
{ tmp[i] = tmp[pos[tmp[m+1]]+i-m-1]; }
int lis = 0;
for(re int i=1; i<=n; i++)
{
tmp[i] += 1;
int res = BIT.query(tmp[i])+1;
BIT.insert(tmp[i],res);
lis = max(lis,res);
}
printf("%lld\n",ans+lis);
return 0;
}
}
signed main()
{ return OMA::main(); }
算了,还是贴一下,虽然没有意义
矩阵快速幂解法
T2
一看就不太可做,留在了最后,瞎打了个背包就没看了,然后接着去调T1了。
别人的想法:
同余最短路,能拿90pts,但好像是假的,然而这玩意我都听都没听过,更别提考场写出来
正解:
%%%沈学长。
是个dp,还没写出来,所以先咕了。
dp实质上是在一个图上跑,平常的dp都是有拓扑序,所以可以直接for循环来转移,想这种出现环的,需要跑个最短路来转移。
Code
咕咕咕
T3
考场直接树剖+线段树,然后打着打着发现思路卡壳了,没继续往下想,因为T1自己的想法还没打,所以干脆拿树剖求了个LCA,丢了个暴力就跑回T1了。
正解:
就是个线段树。
首先不难发现,一个节点 \(u\) 的权值,只有可能对自己的子树产生贡献,所以当一个节点 \(u\) 被修改之后,先用 \(u\) 的权值去更新 \(u\) 的子树,然后去暴力跳 \(u\) 的爹 \(fa\) ,那么\(fa\) 的子树中,除去 \(u\) 的子树那一部分都可以用 \(fa\) 的权值去更新,如果 \(fa\) 的子树在修改前就已经有黑点的话,就说明 \(fa\) 的父亲们或者说祖先之前肯定更新过了子树,就不再接着跳了。
说白了就是区间修改+单点查询
Code
#include<cstdio>
#include<cstring>
#include<climits>
#define MAX 100010
#define re register
#define INF INT_MIN
int n,m;
int w[MAX];
bool vis[MAX];
struct graph
{
int next;
int to;
}edge[MAX<<1];
int cnt=1,head[MAX];
inline void add(int u,int v)
{ edge[++cnt] = (graph){head[u],v},head[u] = cnt; }
namespace FTC
{
int fa[MAX],size[MAX],dfn[MAX];
inline void dfs(int u,int fat)
{
size[u] = 1,dfn[u] = ++cnt,fa[u] = fat;
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(v!=fat)
{ dfs(v,u); size[u] += size[v]; }
}
}
}using namespace FTC;
namespace OMA
{
inline int max(int a,int b)
{ return a>b?a:b; }
class Segment_Tree
{
private:
struct TREE
{
int xam;
int l,r;
int tag;
TREE()
{ xam = -1; }
}st[MAX<<2];
inline int ls(int p)
{ return p<<1; }
inline int rs(int p)
{ return p<<1|1; }
inline void Push_up(int p)
{ st[p].xam = max(st[ls(p)].xam,st[rs(p)].xam); }
inline void Push_down(int p)
{
if(st[p].tag)
{
st[ls(p)].xam = max(st[ls(p)].xam,st[p].tag);
st[ls(p)].tag = max(st[ls(p)].tag,st[p].tag);
st[rs(p)].xam = max(st[rs(p)].xam,st[p].tag);
st[rs(p)].tag = max(st[rs(p)].tag,st[p].tag);
st[p].tag = 0;
}
}
public:
inline void build(int p,int l,int r)
{
st[p].l = l,st[p].r = r;
if(l==r)
{ return ; }
int mid = (l+r)>>1;
build(ls(p),l,mid),build(rs(p),mid+1,r);
}
inline void update(int p,int l,int r,int val)
{
if(l>r)
{ return ; }
if(l<=st[p].l&&st[p].r<=r)
{ st[p].xam = max(st[p].xam,val); st[p].tag = max(st[p].tag,val); return ; }
Push_down(p);
int mid = (st[p].l+st[p].r)>>1;
if(l<=mid)
{ update(ls(p),l,r,val); }
if(r>mid)
{ update(rs(p),l,r,val); }
Push_up(p);
}
inline int query(int p,int pos)
{
if(st[p].l==st[p].r)
{ return st[p].xam; }
Push_down(p);
int ans = INF,mid = (st[p].l+st[p].r)>>1;
if(pos<=mid)
{ ans = max(ans,query(ls(p),pos)); }
else
{ ans = max(ans,query(rs(p),pos)); }
return ans;
}
inline void modify(int u)
{
update(1,dfn[u],dfn[u]+size[u]-1,w[u]);
while(!vis[u]&&fa[u])
{
vis[u] = 1;
update(1,dfn[fa[u]],dfn[u]-1,w[fa[u]]);
update(1,dfn[u]+size[u],dfn[fa[u]]+size[fa[u]]-1,w[fa[u]]);
u = fa[u];
}
}
}Tree;
inline int read()
{
int s=0,w=1; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
return s*w;
}
signed main()
{
n = read(),m = read();
for(re int i=1; i<=n; i++)
{ w[i] = read(); }
for(re int i=1,u,v; i<=n-1; i++)
{ u = read(),v = read(); add(u,v),add(v,u); }
cnt = 0,dfs(1,0);
Tree.build(1,1,n);
for(re int i=1,u; i<=m; i++)
{
char s[10]; scanf("%s",s),u = read();
if(s[0]=='M')
{ Tree.modify(u); }
if(s[0]=='Q')
{ printf("%d\n",Tree.query(1,dfn[u])); }
}
return 0;
}
}
signed main()
{ return OMA::main(); }
反思总结:
打暴力不要占用太长时间,不然留给调试可能是正解的代码的时间会不太够,导致出现问题,打完暴力后,也要先把暴力交上,给自己留条后路。
不要去搞极限操作,指在距离考试结束还有7s交代码,rp不好,就会挂掉,是极大概率挂掉。
有想法就要赶快码出来,不要放到最后。