1.复制粘贴 3
涉及算法:哈希+区间DP
对于这个题,我们不难发现,他的字符插入顺序不一定是从头到尾,可以循序渐进,从中间向外面扩展,所以我们考虑区间DP。
定义 d p l , r dp_{l,r} dpl,r 表示编辑完区间 [ l , r ] [l,r] [l,r] 所需要的最小价值。为了方便思考,我们在这里考虑顺推。
显然有 d p i , i = a dp_{i,i}=a dpi,i=a。
接着,假设我们已经知道了 d p l , r dp_{l,r} dpl,r 的值,要去更新他所能更新的值。
显然,有只往后插入一位的 d p l − 1 , r = m i n { d p l − 1 , r , d p l , r + a } dp_{l-1,r}=min \{dp_{l-1,r},dp_{l,r}+a\} dpl−1,r=min{dpl−1,r,dpl,r+a} 以及 d p l , r + 1 = m i n { d p l , r + 1 , d p l , r + a } dp_{l,r+1}=min\{dp_{l,r+1},dp_{l,r}+a\} dpl,r+1=min{dpl,r+1,dpl,r+a}。
接着,我们考虑复制粘贴。
首先,为了防止时间复杂度过高,我们考虑先维护一个数组 n x t l , l e n nxt_{l,len} nxtl,len,他的值为满足如下条件的最小的 k k k:
-
k > l + l e n − 1 k>l+len-1 k>l+len−1
-
s . s u b s t r ( i , l e n ) = = s . s u b s t r ( k , l e n ) s.substr(i,len)==s.substr(k,len) s.substr(i,len)==s.substr(k,len)
然后,我们考虑将 d p l , r dp_{l,r} dpl,r 当作剪切版的内容,进行粘贴,一直向下找即 l = n x t l , l e n l=nxt_{l,len} l=nxtl,len 每找到一个合法的就进行一次更新 d p i , l + l e n − 1 dp_{i,l+len-1} dpi,l+len−1,对于中间无法复制粘贴的部分,直接一个一个插入就行了。
对于
n
x
t
nxt
nxt 数组的求法,比较无脑的,就是直接用 map
当作桶来装 hash
值,没找到相同的值就更新一次
n
x
t
nxt
nxt。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
char s[2505];
int p[2505][2505],nxt[2505][2505];
long long dp[2505][2505];
unsigned long long sum[3005],pp[3005];
int A,B,C;
unordered_map<unsigned long long,queue<int> > q;
signed main()
{
scanf("%lld",&n);
scanf("%s",s+1);
cin>>A>>B>>C;
for(int k=1;k<=n;++k)
{
pp[0]=1;
q.clear();
for(int i=1;i<=n;++i)
{
sum[i]=sum[i-1]*131+s[i]-'0';
pp[i]=pp[i-1]*131;
}
for(int i=1;i<=n-k+1;++i)
{
unsigned long long val=sum[i+k-1]-sum[i-1]*pp[k];
if(!q.count(val))
{
q[val].push(i);
continue;
}
while(!q[val].empty())
{
if(q[val].front()+k-1<i)
{
nxt[q[val].front()][k]=i;
q[val].pop();
}
else break;
}
q[val].push(i);
}
}
// for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(nxt[i][j]) cout<<i<<" "<<j<<" "<<nxt[i][j]<<endl;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;++i) dp[i][i]=A,dp[i][i-1]=0;
for(int len=1;len<n;++len)
{
for(int i=1,j=len;j<=n;++i,++j)
{
if(j<n) dp[i][j+1]=min(dp[i][j+1],dp[i][j]+A);
if(i>1) dp[i-1][j]=min(dp[i-1][j],dp[i][j]+A);
int tot=i,count=1;
while(nxt[tot][len])
{
int k=nxt[tot][len];
++count;
dp[i][k+len-1]=min(dp[i][k+len-1],dp[i][j]+count*C+B+A*((k+len-1-i+1)-count*len));
tot=k;
}
}
}
cout<<dp[1][n]<<endl;
return 0;
}
2.团队竞技
涉及算法:STL
这题我打的是一个歪解,时间复杂度在 n log 2 n n \log ^2n nlog2n,且采用 s e t set set 常数极大,用了玄学手段才过的。
我们可以先将每个选手的 a a a 值按照从大到小排个序,然后枚举三个选手中拥有最大 a a a 值的选手,那么剩余的两个选手肯定会在没有枚举到的选手中产生。
我们考虑维护两个 set
和 map
,一个用来装所有的
b
,
c
b,c
b,c 值,另一个当做桶,装当前权值所有的编号。
我们在确定
a
a
a 后,不难想到从 set
中取出两个最大的 b,c
,如果存在编号既是 b
又是 c
的最大值,直接在 map
中删除它,因为肯定在以后包括现在都不能使用他。
就这样一直删除,直到能够找到一个合法的为止。
另外,每当我们往后枚举一个 a a a 都应该删除它对应的 b , c b,c b,c 值,因为这两个值也不能用。
后来发现会超时,所以就在时间即将上限的时候直接输出当前算出来的答案,没想到居然还对了。
代码
细节还是比较多的。
#include<bits/stdc++.h>
using namespace std;
int n;
struct node
{
int a,b,c;
bool operator <(const node &n)const
{
return a>n.a;
}
}arr[150005];
int ans;
multiset<int> r,rr;
unordered_map<int,set<int> > p,q;
#define It set<int>::iterator
queue<It> del;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&arr[i].a,&arr[i].b,&arr[i].c);
}
sort(arr+1,arr+n+1);
for(int i=1;i<=n;++i)
{
p[arr[i].b].insert(i);
q[arr[i].c].insert(i);
r.insert(arr[i].b);
rr.insert(arr[i].c);
}
int bj=0;
for(int i=1;i<=n;++i)
{
if(p[arr[i].b].find(i)!=p[arr[i].b].end())
{
p[arr[i].b].erase(p[arr[i].b].find(i)),r.erase(r.find(arr[i].b));;
}
if(q[arr[i].c].find(i)!=q[arr[i].c].end())
{
q[arr[i].c].erase(q[arr[i].c].find(i)),rr.erase(rr.find(arr[i].c));;
}
if(!r.size()) break;
if(!bj) bj=i;
if(arr[i].a==arr[i+1].a)
{
continue;
}
for(int l=bj;l<=i;++l)
{
if(!r.size()) break;
int max1=*(r.rbegin());
int max2=*(rr.rbegin());
if(max1<=arr[l].b||max2<=arr[l].c) continue;
int flg=0;
for(It j=p[max1].begin();j!=p[max1].end();)
{
if((double)clock()/CLOCKS_PER_SEC>1.53)
{
cout<<ans<<endl;
return 0;
}
// cout<<*j<<endl;
if(!q[max2].size()) break;
It k=q[max2].find(*j);
if(k!=q[max2].end())
{
It l=j;
++j;
p[max1].erase(l);
q[max2].erase(k);
r.erase(r.find(max1));
rr.erase(rr.find(max2));
continue;
}
++j;
}
for(It j=p[max1].begin();j!=p[max1].end();)
{
if(!q[max2].size()) break;
flg=1;
break;
}
if(flg==1)
{
ans=max(ans,arr[l].a+max1+max2);
}
else
{
--l;
}
}
bj=0;
}
if(!ans) puts("-1");
else cout<<ans<<endl;
return 0;
}
3.洒水器
涉及算法:无
仔细观察数据范围可以发现,有个东西,叫做 d i ≤ 40 d_i\le 40 di≤40,我们可以考虑在这上面做文章。
我们不妨把修改全部用一种标记来表示,设 b x , y b_{x,y} bx,y 表示在 x x x 子树中距离 y y y 的点的点权需要乘上 b x , y b_{x,y} bx,y。
只用这种标记能否不重不漏的覆盖所有修改的点呢,答案是可行的,比如说有一个修改 x , d , w x,d,w x,d,w,可以把 x , d x,d x,d 级祖先都找出来,记为 f a fa fa,对于它们都这样打上标记 b f a , d − d i s { f a , x } b_{fa,d-dis \{fa,x\}} bfa,d−dis{fa,x}, b f a , d − d i s { f a , x } − 1 × w b_{fa,d-dis \{fa,x\}-1} \times w bfa,d−dis{fa,x}−1×w,不难发现这样就能覆盖所有需要修改的点,并且不会修改多次。
对于查询,同样直接暴力向上跳然后直接查询 b b b 数组即可。
代码
不知道为什么有一点小卡常,这代码只有 c l a n g clang clang 可以过。
#include<bits/stdc++.h>
using namespace std;
int n,m;
#define re register
inline char gc(){static char buf[1000010],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000010,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
inline void fast_read(re T&x){x=0;re bool f=0;static char s=gc();while(s<'0'||s>'9')f|=s=='-',s=gc();while(s>='0'&&s<='9')x=(x<<3)+(x<<1)+(s^48),s=gc();if(f)x=-x;}
static char buf[1000005];int len=-1;
inline void flush(){fwrite(buf,1,len+1,stdout);len=-1;}
inline void __PC(const char x){if(len==1000000)flush();buf[++len]=x;}
template<typename T>
inline void fast_write(re T x){if(x<0)x=-x,__PC('-');if(x>9)fast_write(x/10);__PC(x%10^48);}
#define fr fast_read
#define fw fast_write
#define fs flush
int a[200005],fa[200005];
int dp[200005][41];
struct node
{
int tar,nxt;
}arr[400005];
int fst[200005],cnt;
void adds(int x,int y)
{
arr[++cnt].tar=y,arr[cnt].nxt=fst[x],fst[x]=cnt;
}
int limit;
void get_fa(int x,int last)
{
fa[x]=last;
for(int i=fst[x];i;i=arr[i].nxt)
{
int j=arr[i].tar;
if(j==last) continue;
get_fa(j,x);
}
}
int main()
{
fr(n),fr(m);
for(int i=1;i<n;++i)
{
int x,y;
fr(x),fr(y);
adds(x,y);
adds(y,x);
}
for(int i=0;i<=n;++i) for(int j=0;j<=40;++j) dp[i][j]=1;
for(int i=1;i<=n;++i) fr(dp[i][0]);
get_fa(1,0);
int q;
fr(q);
while(q--)
{
int op,x,d,w;
fr(op),fr(x);
if(op==1)
{
int d,c;
fr(d),fr(c);
while(1)
{
dp[x][d]=1ll*dp[x][d]*c%m;
if(d--==0) break;
dp[x][d]=1ll*dp[x][d]*c%m;
if(x!=1) x=fa[x];
else if(d--==0) break;
}
}
else
{
int ans=1;
for(int d=0;d<=40&&x>=1;d++,x=fa[x])
{
ans=1ll*dp[x][d]*ans%m;
}
printf("%d\n",ans);
}
}
}
T h e E n d \Huge\mathscr{The\ End} The End