2021.10.1&2 国庆集训测试
考得都不咋地,2号的考试甚至创下了新的记录,28分!好家伙,要不是USACO有友情分(测试点1永远是样例),我就爆零了。实在悲惨。说实话,突然感觉提高组非常虚。
不过说实话,这六道题都不算太难,至少大多数题代码打起来都很简单(2KB不到吧),也没有太***钻的思路;现在反思看来,每道题我都是想到正解,至少是往正解的方向想了的,但就是没把思路贯通,永远梗着。不过每道题在听完点拨之后,我都可以在半个小时(甚至二十分钟)以内通过题目。综上,我自认为代码能力问题不大,主要就是练题太少,以后得加把劲,多练题,以及多总结。
好吧,上题解。
Day 1 动态规划专题
T1 snakes
暴力即可,当时是打了个\(O(N^3)\)的暴力DP,考场上经过极其不严谨的计算认为会超时,就一直在想如何优化(比如用线段树优化到\(O(N^2logN)\)),于是就花费了许多时间,最后评测,惊喜意外莫名满分。
没什么好说的。
考虑dp[i][j]表示前i组蛇用j次切换机会的最少浪费,枚举转移点,更新。
#include<cstdio>
#include<cstring>
//#define zczc
using namespace std;
const int N=610;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int min(int s1,int s2){
return s1<s2?s1:s2;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
int m,n,a[N],sum[N],dp[N][N];
namespace st{
int b[N][10],log[N];
void init(){
for(int i=1;i<=m;i++){
b[i][0]=a[i];
}
for(int i=1;i<10;i++){
for(int j=1;j+(1<<i)-1<=m;j++){
b[j][i]=max(b[j][i-1],b[j+(1<<i-1)][i-1]);
}
}
log[0]=-1;
for(int i=1;i<=m;i++){
log[i]=log[i>>1]+1;
}
return;
}
inline int work(int l,int r){
int size=log[r-l+1];
return max(b[l][size],b[r-(1<<size)+1][size]);
}
}
inline int cost(int l,int r){
return st::work(l,r)*(r-l+1)-sum[r]+sum[l-1];
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++){
read(a[i]);
sum[i]=sum[i-1]+a[i];
}
st::init();
memset(dp,0x3f,sizeof(dp));
int maxn=0;
dp[0][0]=0;
for(int i=1;i<=m;i++){
dp[i][0]=cost(1,i);
for(int j=min(i,n);j;j--){
for(int k=i;k;k--){
dp[i][j]=min(dp[i][j],dp[k-1][j-1]+cost(k,i));
}
}
}
int ans=1e9;
for(int i=0;i<=n;i++)ans=min(ans,dp[m][i]);
printf("%d\n",ans);
return 0;
}
T2 Exercise
听完讲解之后,感觉好神奇,这可是正常人能想出来的方法。
首先把问题转化成:
假如有一个正整数集合A,满足\(\sum{A_i}\le m\),那么集合内所有元素的gcd,假设为b,就称b是符合条件的数,求所有b的和,取模。
想到这一步很容易,接下来就很玄乎了。它是说把这个东西转化成背包问题,每个数相当于是一个物品,向后依次更新即可。
然后就有个问题是这样更新出来的值是错误的,因为相同的gcd可能会由不同的集合A更新,这会导致结果的不准确。想到用欧式筛的同样思路,如何用一种方式,唯一地表示它(或者说找到它的唯一构成方法)呢。于是想到唯一分解定理。把原先正整数物品替换成质数物品,把原来的01背包变成完全背包,即可。
代码超短。
#include<cstdio>
//#define zczc
#define int long long
const int N=10010;
const int M=3000;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
int m,mod,cnt,p[M],dp[M][N];
bool is[N];
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(mod);
for(int i=2;i<=m;i++){
if(is[i])continue;
p[++cnt]=i;
for(int j=i;j<=m;j+=i)is[j]=true;
}
dp[0][0]=1;
for(int i=1;i<=cnt;i++){
for(int j=0;j<=m;j++){
dp[i][j]+=dp[i-1][j];
for(int k=p[i];k<=j;k*=p[i]){
dp[i][j]+=dp[i-1][j-k]*k;
dp[i][j]%=mod;
}
dp[i][j]%=mod;
}
}
int ans=0;
for(int i=0;i<=m;i++){
ans+=dp[cnt][i];ans%=mod;
}
printf("%lld",ans);
return 0;
}
T3 Count the Cows
它竟然是数位DP!
三观尽毁。果然还是我太弱了。
考场没做出来的主要原因是\(\lfloor\frac{x(或y)}{3^k}\rfloor\)%3与其它东西联系起来,也就是没有搞出来它的涵义。于是当时就盯着这个奇怪的式子盯了半天,百思不得其解。
其实想它的意义并不难,\(\lfloor\frac{x(或y)}{3^k}\rfloor\)是指它的三进制意义下砍掉尾巴,而砍掉尾巴之后再%3得到的就是某一位上的数字,原理类似于取出十进制数的某一位。
知道这一点之后就简单了,问题就变成了求i的个数,满足\(i\in[0,d]\),且 x+i 和 y+i 三进制意义下每一位的奇偶性都相同。然后就可以想到数位DP求解了。
说起复杂,但代码出奇简单。
#include<cstdio>
#include<cstring>
//#define zczc
#define int long long
const int S=40;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int min(int s1,int s2){
return s1<s2?s1:s2;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
int ax[S],ay[S],ad[S];
int f[S][2][2][2];
int work(int wh,bool la,bool lb,bool e){
if(wh==0)return (la==false)&&(lb==false);
if(f[wh][la][lb][e]>=0)return f[wh][la][lb][e];
int ans=0,end=e?ad[wh]:2;
for(int i=0;i<=end;i++){
for(int sa=0;sa<=1;sa++){
for(int sb=0;sb<=1;sb++){
int nx=ax[wh]+i-la*3+sa;
int ny=ay[wh]+i-lb*3+sb;
if(nx<0||nx>2||ny<0||ny>2)continue;
if((nx&1)!=(ny&1))continue;
ans+=work(wh-1,sa,sb,e&&(i==ad[wh]));
}
}
}
//printf("%lld %d %d %d %lld\n",wh,la,lb,e,ans);
return f[wh][la][lb][e]=ans;
}
inline void get(int wh,int a[]){
for(int i=1;i<S;i++){
a[i]=wh%3;wh/=3;
}
}
void solve(){
int x,y,d;
read(d);read(x);read(y);
get(x,ax);get(y,ay);get(d,ad);
memset(f,-1,sizeof(f));
printf("%lld\n",work(S-1,0,0,true));
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
int test;read(test);
while(test--)solve();
return 0;
}
Day 2 数据结构专题
T1 Snow Boots G
挺简单一道题,考试的时候不知为何走火入魔了,明明有更保险的方法却想着投机取巧,实在是不应该。
如果只有一次询问,那么将原序列转化成一个01序列,其中0表示积雪深度超过靴子承受能力的点,1则相反,那么问题就变成了求最长连续0的个数,并拿这个答案与最长步数进行比较。复杂度\(O(N)\)。
然后考虑多次询问。对于这些询问相互独立的题目,可以考虑使用离线算法。本题就是按每个询问的最大积雪深度进行升序排序,然后在回答询问之前动态更新一些点的01值(一开始所有点都赋为0,表示默认都无法通过),回答即可。
复杂度 \(O(NlogN+Q)\) 。
压行有点冲,见谅。
#include<cstdio>
#include<algorithm>
const int N=100010;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int max(int s1,int s2){return s1<s2?s2:s1;}
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{int l,r,ldata,rdata,data,len;bool all;}t[N<<2];
node operator +(node s1,node s2){
node an;an.l=s1.l,an.r=s2.r,an.len=s1.len+s2.len;
an.data=max(max(s1.data,s2.data),s1.rdata+s2.ldata);
an.ldata=s1.all?s1.len+s2.ldata:s1.ldata;
an.rdata=s2.all?s2.len+s1.rdata:s2.rdata;
an.all=s1.all&&s2.all;return an;
}
void build(int wh,int l,int r){
t[wh].l=l,t[wh].r=r,t[wh].len=t[wh].ldata=t[wh].rdata=t[wh].data=r-l+1,t[wh].all=true;
if(l==r)return;build(lc,l,mid);build(rc,mid+1,r);return;
}
void change(int wh,int pl){
if(t[wh].l==t[wh].r){t[wh].ldata=t[wh].rdata=t[wh].data=t[wh].all=0;return;}
if(pl<=mid)change(lc,pl);else change(rc,pl);t[wh]=t[lc]+t[rc];return;
}
#undef lc
#undef rc
#undef mid
int m,n,now=1,an[N];
struct node1{int wh,s1,s2;}a[N],q[N];
inline bool cmp(node1 s1,node1 s2){return s1.s1<s2.s1;}
signed main(){
read(m);read(n);
for(int i=1;i<=m;i++){read(a[i].s1);a[i].wh=i;}
for(int i=1;i<=n;i++){read(q[i].s1);read(q[i].s2);q[i].wh=i;}
build(1,1,m);sort(a+1,a+m+1,cmp);sort(q+1,q+n+1,cmp);
for(int i=1;i<=n;i++){
while(now<=m&&a[now].s1<=q[i].s1){change(1,a[now].wh);now++;}
an[q[i].wh]=t[1].data<q[i].s2?1:0;
}
for(int i=1;i<=n;i++)printf("%d\n",an[i]);
return 0;
}
T2 No Time to Dry
应该是今天(或者说昨天?【滑稽】)考试最难的一道题了。
主要是它那个结论来得不明不白,鬼知道它是怎么想出来的,以及它的正确性怎么证明。结论如下:
对于数列A,满足\(i\le j, A_i=A_j, max\{A_k\}>A_i(k\in[i+1,j-1])\)三个条件的数对\({i,j}\)的个数为num,那么有结论说涂完这个数列所需要的步数便会是\(|A|-num\)步。
别问这个结论怎么来的,我也不知道,下午周老师评讲的时候直接告诉我们的,也没证明啊。那咱先姑且认为这个结论是正确的,再来考虑这道题。
然后就变成了一个奇怪的区间统计问题。找出区间可以用单调栈线性解决,然后对询问进行离线操作,加上树状数组维护。复杂度为 \(O(NlogN)\) 。
#include<cstdio>
#include<algorithm>
#include<stack>
const int N=200010;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
int m,n,a[N];
struct node{
int wh,data;
};
stack<node>st;
struct sr{
int wh,l,r;
}b[N],q[N];
inline bool cmp(sr s1,sr s2){
return s1.r<s2.r;
}
int cnt,now=1,an[N];
#define lowbit (wh&-wh)
int t[N];
inline void change(int wh,int val){
for(;wh<=m;wh+=lowbit)t[wh]+=val;
}
inline int work(int wh){
int an=0;
for(;wh;wh-=lowbit)an+=t[wh];
return an;
}
#undef lowbit
signed main(){
read(m);read(n);
for(int i=1;i<=m;i++){
read(a[i]);
}
st.push((node){0,-1});
for(int i=1;i<=m;i++){
while(st.top().data>a[i])st.pop();
if(st.top().data==a[i]){
b[++cnt].l=st.top().wh;b[cnt].r=i;st.pop();
}
st.push((node){i,a[i]});
}
for(int i=1;i<=n;i++){
read(q[i].l);read(q[i].r);q[i].wh=i;
}
sort(b+1,b+cnt+1,cmp);
sort(q+1,q+n+1,cmp);
for(int i=1;i<=n;i++){
while(now<=cnt&&b[now].r<=q[i].r){
change(b[now].l,1);
now++;
}
an[q[i].wh]=q[i].r-q[i].l+1-(work(q[i].r)-work(q[i].l-1));
}
for(int i=1;i<=n;i++)printf("%d\n",an[i]);
return 0;
}
T3 Cow Land
拿到题目之后非常开心,哎呀这道题我似乎看过,我知道这道题可以用“树上前缀异或和”来解决,那不就简单了吗……
于是拿了8分的好成绩。原因竟是因为忘了把两个前缀异或起来之后还要异或上lca的值。太凄惨了。
这道题主要运用了异或这个运算的特性,说具体一点,就是A^A=0,A^0=A 。于是考虑把路径上问题转化成两个前缀的问题。记住,一定要再异或上它们的lca的值!至于修改,上dfs序和线段树即可。
复杂度\(O(NlogN)\),比标准做法树链剖分\(O(NlogN^2)\)还要少一个log。真好。
#include<cstdio>
//#define zczc
const int N=100010;
const int S=20;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
int m,n,a[N];
struct edge{
int t,next;
}e[N<<1];
int esum,head[N];
inline void add(int fr,int to){
esum++;
e[esum].t=to;
e[esum].next=head[fr];
head[fr]=esum;
return;
}
int cnt,b[N],f[N],s[N],id[N],size[N];
void dfs(int wh,int fa){
f[wh]=fa;
b[wh]=b[fa]^a[wh];
id[wh]=++cnt;
s[cnt]=wh;
size[wh]=1;
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(th==fa)continue;
dfs(th,wh);
size[wh]+=size[th];
}
return;
}
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{
int l,r,data;
}t[N<<2];
inline void pushnow(int wh,int val){
t[wh].data^=val;return;
}
inline void pushdown(int wh){
if(t[wh].data){
pushnow(lc,t[wh].data);
pushnow(rc,t[wh].data);
t[wh].data=0;
}
return;
}
void build(int wh,int l,int r){
t[wh].l=l,t[wh].r=r,t[wh].data=0;
if(l==r){
t[wh].data=b[s[l]];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
return;
}
void change(int wh,int wl,int wr,int val){
//printf("w:%d %d %d %d\n",wh,wl,wr,val);
if(wl<=t[wh].l&&t[wh].r<=wr){
pushnow(wh,val);
return;
}
pushdown(wh);
if(wl<=mid)change(lc,wl,wr,val);
if(wr>mid)change(rc,wl,wr,val);
return;
}
int work(int wh,int pl){
if(pl==0)return 0;
if(t[wh].l==t[wh].r){
return t[wh].data;
}
pushdown(wh);
if(pl<=mid)return work(lc,pl);
else return work(rc,pl);
}
#undef lc
#undef rc
#undef mid
int d[N],c[N][25];
void init(int wh,int fa,int deep){
c[wh][0]=fa;d[wh]=deep;
for(int i=1;i<=S;i++){
c[wh][i]=c[c[wh][i-1]][i-1];
}
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(th==fa)continue;
init(th,wh,deep+1);
}
return;
}
inline void swap(int &s1,int &s2){
int s3=s1;s1=s2;s2=s3;return;
}
inline int lca(int s1,int s2){
if(d[s1]<d[s2])swap(s1,s2);
for(int i=S;i>=0;i--){
if(d[c[s1][i]]>=d[s2]){
s1=c[s1][i];
}
}
if(s1==s2)return s1;
for(int i=S;i>=0;i--){
if(c[s1][i]!=c[s2][i]){
s1=c[s1][i];
s2=c[s2][i];
}
}
return c[s1][0];
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++)read(a[i]);
int s1,s2;
for(int i=1;i<m;i++){
read(s1);read(s2);
add(s1,s2);add(s2,s1);
}
dfs(1,0);
build(1,1,m);
init(1,0,1);
int op;
while(n--){
read(op);read(s1);read(s2);
if(op==1){
change(1,id[s1],id[s1]+size[s1]-1,a[s1]^s2);
a[s1]=s2;
}
else{
int l=lca(s1,s2);
printf("%d\n",work(1,id[s1])^work(1,id[s2])^a[l]);
}
}
return 0;
}