学科营训练6解题报告

文章详细解析了两道编程竞赛题目,第一题涉及哈希和区间动态规划,解释了如何利用这两种技术解决字符编辑问题;第二题利用STL中的数据结构解决团队竞技问题,提出了一个时间复杂度较高的解决方案;第三题通过观察数据范围,设计了一种处理洒水器问题的巧妙方法。
摘要由CSDN通过智能技术生成

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\} dpl1,r=min{dpl1,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+len1

  • 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+len1,对于中间无法复制粘贴的部分,直接一个一个插入就行了。

对于 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 值的选手,那么剩余的两个选手肯定会在没有枚举到的选手中产生。

我们考虑维护两个 setmap,一个用来装所有的 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 di40,我们可以考虑在这上面做文章。

我们不妨把修改全部用一种标记来表示,设 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,ddis{fa,x} b f a , d − d i s { f a , x } − 1 × w b_{fa,d-dis \{fa,x\}-1} \times w bfa,ddis{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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值