多校第三场

hdu4628:状压dp的题,比赛时没有看出来,一直在想用区间dp来写,后来发现区间dp划分子状态都是一些子区间,并没有考虑到取的时候会因为选择的方式不同对后来的方案会产生影响。比如adcedacf,第一次选adeda和adcea的结果是不同的,这在区间状态转移是我只考虑了dp[i][j]=min(dp[i-1][j-1](a[i]==a[j]),dp[i+1][k-1](a[i]==a[k])ordp[i][k]+dp[k+1][j-1]ordp[k+2][j-1](ifa[k+1]==a[j]))

状态压缩后能找出所有的回文子序列,每种状态都能通过减去回文子序列而得到子状态。

代码:

#include<iostream>
#include<vector>
#include<string>
#include<queue>
#include<cstdio>
#include<cstring>
#define maxn 20
#define MAX_N 1<<16
#define INF 0xfffffff
using namespace std;
int n;
bool can[MAX_N];
char s[maxn];
int dp[MAX_N];
int work()
{
    //记录所有的回文子串
    int cnt=0;
    for(int i=0;i<(1<<n);i++)
    {
        char cur[maxn];
        cnt=0;
        for(int j=0;j<n;j++)
        {
            if(i&(1<<j))
            {
                cur[cnt++]=s[j];
            }
        }
        int flag=1;
        for(int l=0,r=cnt-1;l<r;l++,r--)
        {
            if(cur[l]!=cur[r])
            {
                flag=0;
                break;
            }
        }
        can[i]=flag;//can都得赋值
    }
    //判断每种状态的最小移除数
    dp[0]=0;
    for(int i=1;i<(1<<n);i++)
    {
        dp[i]=INF;
        for(int subset=i;subset>0;(--subset)&=i)//取所有的子状态
        {
            if(can[subset])
            {
                dp[i]=min(dp[i],dp[i-subset]+1);
            }
        }
    }
    return dp[(1<<n)-1];
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>s;
        n=strlen(s);
        cout<<work()<<endl;
    }
	return 0;
}

hdu4627 数论的题吧,没有yy的思想,只好暴力枚举啦。

估计最大的数可能出现在n/2附近吧。

代码:

#include<iostream>
#define ll long long
using namespace std;
int gcd(int a,int b)
{
    if(b==0) return a;
    else return gcd(b,a%b);
}
int main()
{
    int t,n;
    cin>>t;
    while(t--)
    {
        cin>>n;
        if(n/2<10)
        {
            ll num1=n/2,num2,ans=-1,m;
            while(num1<n)
            {
                num2=n-num1;
                m=num1*num2/gcd(num1,num2);
                ans=max(ans,m);
                num1++;
            }
            cout<<ans<<endl;
        }
        else
        {
            ll num1=n/2,num2,ans=-1,m;
            while(num1<n/2+10)
            {
                num2=n-num1;
                m=num1*num2/gcd(num1,num2);
                ans=max(ans,m);
                num1++;
            }
            cout<<ans<<endl;
        }
    }
    return 0;
}

hdu4630:

数据小点的话可以用dp写,状态转移方程也是比较简单的,比赛时没想过,dp的思想还是比较弱呀~~~

dp[i][j]=max(max(dp[i+1][j],dp[i][j-1]),gcd(a[i],a[j]));最大公约数可能是区间的两边最大,或者是在不含同时含a[i]与a[j]的区间。以后还是可以好好想想这种区间dp和数论结合的情况。但是这里50000开二维的存不下吧。

标称给的dp代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
using namespace std;

const int MAX_N = 500 + 10;
int a[MAX_N], n;
int dp[MAX_N][MAX_N];

void work() {
	cin >> n;
	for (int i = 0; i < n; ++i) {
		cin >> a[i];
	}
	for (int i = n - 1; i >= 0; --i) {
		dp[i][i] = 0;
		for (int j = i + 1; j < n; ++j) {
			dp[i][j] = max(max(dp[i + 1][j], dp[i][j - 1]), __gcd(a[i], a[j]));
		}
	}
	int nQ;
	cin >> nQ;
	while (nQ--) {
		int l, r;
		cin >> l >> r;
		--l, --r;
		cout << dp[l][r] << endl;
	}
}

int main() {
	int T;
	cin >> T;
	while (T--)
		work();
	return 0;
}
下面用树状数组的思想来写。不过这题跟以前写的树状数组的题还是有很大区别的,可能是做的题太少的原因吧,根本有没想到怎么使用树状数组,只想到每次询问和线段树,树状数组的题很像。

这里用树状数组来记录一段区间内最大可能的两个数的最大公约数。一开始从询问的最右边更新,把所有在这个区间的询问解决掉,然后再向左移动。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 50005
using namespace std;
int n,m;
struct query
{
    int l,r,id;
}q[maxn];
int ans[maxn];
int a[maxn],b[maxn],c[maxn];//a[]记录原始数组,b[k]记录k的第一个倍数出现的位置,c[i]记录从i开始到n最大的公约数
int lowbit(int x)
{
    return x&(-x);
}
void updata(int x,int val)//更新最大值
{
    while(x<=n)
    {
        c[x]=max(val,c[x]);
        x+=lowbit(x);
    }
}
int Max(int x)
{
    int ret=-1;
    while(x>0)
    {
        ret=max(ret,c[x]);
        x-=lowbit(x);
    }
    return ret;
}
bool cmp(query x,query y)
{
    return x.l>y.l;
}
void work()
{
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    int i=n,j=1;
    while(j<=m)//每次从询问的靠右边的l开始
    {
        while(i>0&&i>=q[j].l)
        {
            for(int k=1;k*k<=a[i];k++)
            {
                if(a[i]%k==0)
                {
                    if(b[k]!=0)
                    {
                        updata(b[k],k);
                    }
                    b[k]=i;
                    if(k!=a[i]/k)//平方的情况会重复
                    {
                        if(b[a[i]/k]!=0)//a[i]的另一个因数
                        {
                            updata(b[a[i]/k],a[i]/k);
                        }
                        b[a[i]/k]=i;
                    }
                }
            }
            i--;
        }
        while(j<=m&&q[j].l>i)//使得q[j].l这个区间总是被事先处理了的
        {
            ans[q[j].id]=Max(q[j].r);
            j++;
        }
    }
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}
int main()
{
    int t,l,r;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&q[i].l,&q[i].r);
            q[i].id=i;
        }
        //按照l从大到小排序,这样可以每次先更新最右边的,并求出解
        sort(q+1,q+1+m,cmp);
        work();
    }
    return 0;
}

还有几题,得补充一些知识,正在学习中。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值