作为线下选手,非常不要脸的写一份题解……
A、hdu5690
题意:求m个x组成的数模k是否等于c
m<=10^10,k<=10000
题解:两种做法,
第一种,裸的矩阵乘法,构造矩阵
{f(x,i),x}*{10 0}={f(x,i+1),x}
{1 1}
复杂度O(Tlog m)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
int T;
int x,mod,C;
long long m;
struct yts
{
int x,y;
int a[3][3];
yts operator*(yts b)
{
yts ans;
ans.x=x;ans.y=b.y;
memset(ans.a,0,sizeof(ans.a));
for (int i=1;i<=x;i++)
for (int j=1;j<=b.y;j++)
for (int k=1;k<=y;k++)
ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
return ans;
}
}a,b,c;
int main()
{
scanf("%d",&T);
for (int j=1;j<=T;j++)
{
scanf("%d%I64d%d%d",&x,&m,&mod,&C);
printf("Case #%d:\n",j);
a.x=1;a.y=2;a.a[1][1]=0;a.a[1][2]=x;
b.x=2;b.y=2;b.a[1][1]=10;b.a[2][1]=1;b.a[1][2]=0;b.a[2][2]=1;
c.x=2;c.y=2;c.a[1][1]=c.a[2][2]=1;c.a[1][2]=c.a[2][1]=0;
while (m)
{
if (m&1) c=c*b;
b=b*b;
m>>=1;
}
a=a*c;
if (a.a[1][1]==C) printf("Yes\n"); else printf("No\n");
}
return 0;
}
第二种,找循环节
暴力找循环节,不过循环节的长度不确定,所以复杂度不保证。
如果哪位神犇能够证明循环节小于等于k的话,欢迎交流。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
int T;
int x,mod,c;
int dep[10010];
long long m;
int main()
{
scanf("%d",&T);
for (int j=1;j<=T;j++)
{
scanf("%d%I64d%d%d",&x,&m,&mod,&c);
printf("Case #%d:\n",j);
memset(dep,0,sizeof(dep));
int now=0,cnt=0;
for (;m>0;)
{
now=(now*10+x)%mod;cnt++;m--;
if (!dep[now]) dep[now]=cnt;
else m%=(long long)(cnt-dep[now]);
}
if (now==c) printf("Yes\n"); else printf("No\n");
}
return 0;
}
B、hdu5691
题意:给你N个数,告诉你第i个数只能放在第p[i]个位置,或者没有限制,要求最大化∑a[i]*a[i+1]
n<=16
题解:数据范围这么小,状压dp可过,f[i][j][S]表示考虑到第i个数,第i个数为j,前面用过的数的状态为S的最大值
如果哪位神犇想出了复杂度更优的做法(多项式算法),欢迎交流。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 1<<16
#define inf 0x7fffffff
using namespace std;
int a[20],p[20];
int f[17][17][maxn];
int n,m,T;
int Count(int s)
{
int num=0;
for (int i=0;i<n;i++) if (s&(1<<i)) num++;
return num;
}
void add(int &x,int y) {x=max(x,y);}
int main()
{
scanf("%d",&T);
int Tcase=0;
while (T--)
{
Tcase++;
scanf("%d",&n);
for (int i=1;i<=n;i++) {scanf("%d%d",&a[i],&p[i]);if (p[i]!=-1) p[i]++;}
printf("Case #%d:\n",Tcase);
int mx=(1<<n);
for (int i=0;i<=n;i++)
for (int j=0;j<=n;j++)
for (int s=0;s<mx;s++)
f[i][j][s]=-inf;
f[0][0][0]=0;
for (int i=0;i<n;i++)
for (int s=0;s<mx;s++)
if (Count(s)==i)
for (int j=0;j<=n;j++)
if (f[i][j][s]!=-inf)
for (int k=1;k<=n;k++)
if ((p[k]==i+1 || p[k]==-1) && !((s>>(k-1))&1)) add(f[i+1][k][s|(1<<(k-1))],f[i][j][s]+a[j]*a[k]);
int ans=-inf;
for (int i=1;i<=n;i++) ans=max(ans,f[n][i][mx-1]);
printf("%d\n",ans);
}
return 0;
}
C、hdu5692
题意:给定一棵树,每个点有点权,m个询问,修改点权,或求一条路径起点为根,经过x点,且权值和最大。
n,m<=10^5
题解:非常裸的dfs序
建出入栈序和出栈序,根节点到一个节点的路径的权值和,即为根节点到入栈序的前缀和。
于是,变成了线段树区间修改、区间查询最大值的裸题。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 100010
#define inf 100000000000000ll
using namespace std;
struct yts
{
int l,r;
long long mx,tag;
}t[4*maxn];
int head[maxn],to[2*maxn],nxt[2*maxn];
long long w[maxn],sum[maxn],a[maxn];
int in[maxn],out[maxn];
int n,m,num,tot,T;
void addedge(int x,int y)
{
num++;to[num]=y;nxt[num]=head[x];head[x]=num;
}
void dfs(int x,int fa)
{
in[x]=++tot;
for (int p=head[x];p;p=nxt[p])
if (to[p]!=fa) dfs(to[p],x);
out[x]=tot;
}
void add(int i,long long x)
{
t[i].tag+=x;t[i].mx+=x;
}
void release(int i)
{
if (t[i].tag==0) return;
add(i*2,t[i].tag);add(i*2+1,t[i].tag);
t[i].tag=0;
}
void build(int i,int l,int r)
{
t[i].l=l;t[i].r=r;t[i].tag=0;
if (l==r) {t[i].mx=sum[l];return;}
int mid=(l+r)/2;
build(i*2,l,mid);build(i*2+1,mid+1,r);
t[i].mx=max(t[i*2].mx,t[i*2+1].mx);
}
void modify(int i,int l,int r,long long x)
{
if (l<=t[i].l && t[i].r<=r) {add(i,x);return;}
release(i);
int mid=(t[i].l+t[i].r)/2;
if (l<=mid) modify(i*2,l,r,x);
if (mid<r) modify(i*2+1,l,r,x);
t[i].mx=max(t[i*2].mx,t[i*2+1].mx);
}
long long query(int i,int l,int r)
{
if (l<=t[i].l && t[i].r<=r) return t[i].mx;
release(i);
int mid=(t[i].l+t[i].r)/2;
long long ans=-inf;
if (l<=mid) ans=max(ans,query(i*2,l,r));
if (mid<r) ans=max(ans,query(i*2+1,l,r));
return ans;
}
int main()
{
scanf("%d",&T);
int Tcase=0;
while (T--)
{
Tcase++;
num=tot=0;
memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;y++;
addedge(x,y);addedge(y,x);
}
for (int i=1;i<=n;i++) scanf("%I64d",&w[i]);
printf("Case #%d:\n",Tcase);
dfs(1,0);
memset(a,0,sizeof(a));
for (int i=1;i<=n;i++) a[in[i]]+=w[i],a[out[i]+1]-=w[i];
for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
build(1,1,n);
while (m--)
{
int op,x;
long long y;
scanf("%d",&op);
if (op==0)
{
scanf("%d%I64d",&x,&y);
x++;
modify(1,in[x],out[x],-w[x]+y);
w[x]=y;
}
else
{
scanf("%d",&x);
x++;
printf("%I64d\n",query(1,in[x],out[x]));
}
}
}
return 0;
}
D、hdu5693
题意:给你一个数列和若干个公差,每次选出一段连续的数,要求这段数是一个等差数列,且公差在给定的公差集合之中,问最多删除多少个数?
n,m<=300
题解:看到这种题,很明显就是dp了,而且是标准的区间dp类型。
预处理出dp[i][j]表示i到j中的数能否全部删除,最后一个线性dp就可以计算出最终答案
一开始自己想的状态是对的,但是感觉转移需要一个结论。
性质:如果一次操作中,我们删除了>=4个数,我们必然可以把这次操作分成多次,每次只删除2或3个数。
于是,就可以dp了。
dp[i][i]=0,dp[i][i-1]=1
如果满足以下情况之一,则dp[i][j]=1,否则dp[i][j]=0
dp[i][k]&&dp[k+1][j] i<k<j
若删除的等差数列为2项
dp[i+1][j-1]&&(d==a[j]-a[i])
若删除的等差数列为3项
a[j]-a[k]==a[k]-a[i]==d && dp[i+1][k-1] && dp[k+1][j-1]
其中,d为某个公差,可通过set快速查找
O(n^3logm)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#define maxn 310
using namespace std;
bool dp[maxn][maxn];
set<int> p;
int a[maxn],f[maxn];
int n,m,T;
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
p.clear();
for (int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
p.insert(x);
}
memset(dp,0,sizeof(dp));
for (int i=1;i<=n;i++) dp[i][i]=0,dp[i][i-1]=1;
for (int len=1;len<=n;len++)
for (int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
dp[i][j]=0;
for (int k=i+1;k<j;k++) dp[i][j]|=dp[i][k]&&dp[k+1][j];
if (p.count(a[j]-a[i])) dp[i][j]|=dp[i+1][j-1];
if ((a[j]-a[i])%2==0 && p.count((a[j]-a[i])/2))
for (int k=i+1;k<j;k++) if (a[j]-a[k]==a[k]-a[i]) dp[i][j]|=dp[i+1][k-1]&&dp[k+1][j-1];
}
f[0]=0;
for (int i=1;i<=n;i++)
{
f[i]=0;
for (int j=1;j<=i;j++) if (dp[j][i]) f[i]=max(f[i],f[j-1]+i-j+1);
f[i]=max(f[i],f[i-1]);
}
printf("%d\n",f[n]);
}
return 0;
}
E、hdu5694
题意:给你一个字符串构造,求前L到R位有多少个B。
T<=1000,L<=R<=10^18
题解:
len[i]表示第i个串的长度,f[i]表示第i个串中B的数量
len[i]=2*len[i-1]+1
f[i]=f[i-1]+1+len[i-1]-f[i-1]
len这个数组增长的非常快,所以最多只会用到60左右
calc2(i,j)表示计算第i个串前j个字符中有多少个B
若len[i-1]>=j,calc2(i,j)=calc2(i-1,j)
否则,calc2(i,j)=f[i-1]+1+j-len[i-1]-1-calc1(i-1,j-len[i-1]-1)
calc1(i,j)表示计算第i个串倒数j个字符中有多少个B
若len[i-1]>=j,cacl1(i,j)=j-calc2(i-1,j)
否则,calc1(i,j)=len[i-1]-f[i-1]+1+calc1(i-1,j-len[i-1]-1)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
long long len[70],f[70];
long long l,r;
int T;
long long calc(int i,long long j,int op)
{
if (i==0 || j<=0) return 0ll;
long long ans;
if (op)
{
if (len[i-1]>=j) ans=calc(i-1,j,1);
else ans=f[i-1]+1ll+j-len[i-1]-1ll-calc(i-1,j-len[i-1]-1ll,0);
}
else
{
if (len[i-1]>=j) ans=j-calc(i-1,j,1);
else ans=len[i-1]-f[i-1]+1ll+calc(i-1,j-len[i-1]-1ll,0);
}
return ans;
}
int main()
{
len[1]=1ll;f[1]=1ll;
for (int i=2;i<=60;i++) len[i]=len[i-1]*2ll+1ll,f[i]=len[i-1]+1ll;
scanf("%d",&T);
while (T--)
{
scanf("%I64d%I64d",&l,&r);
printf("%I64d\n",calc(60,r,1)-calc(60,l-1,1));
}
return 0;
}
F、hdu5695
题意:一个1到n的排列,其权值定义为所有前缀最小值之和,现在给出一些限制x不能在y之前,求权值最大为多少?
n,m<=10^5
题解:贪心
首先,大的数放在前面必然比小的数放在前面优。
从前往后枚举每个位置,每次选择能放的数中的最大值,用优先队列维护一下。每次放入一个数,就将对应的限制去掉,类似拓扑排序的过程。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#define maxn 100010
using namespace std;
int head[maxn],to[maxn],nxt[maxn];
int n,m,T,num;
long long a[maxn];
int d[maxn];
priority_queue<int> q;
void addedge(int x,int y)
{
num++;to[num]=y;nxt[num]=head[x];head[x]=num;
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
memset(d,0,sizeof(d));
num=0;
memset(head,0,sizeof(head));
for (int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y);d[y]++;
}
for (int i=1;i<=n;i++) if (!d[i]) q.push(i);
for (int i=1;i<=n;i++)
{
a[i]=q.top();q.pop();
for (int p=head[a[i]];p;p=nxt[p]) {d[to[p]]--;if (!d[to[p]]) q.push(to[p]);}
}
long long mn=n+1,ans=0;
for (int i=1;i<=n;i++) mn=min(mn,a[i]),ans+=mn;
printf("%I64d\n",ans);
}
return 0;
}