Problem 1矩形

Description

给出s,A。b[i][j] = s[i] * s[j];请问在这个矩阵b中,有多少子矩形满足其中的b[i][j]的和为另一个给定的数字A。

Solution

在想这道题时,我就想到了,把这个新的矩阵给表示出来。那么就是
(s[1]*s[1]) (s[1]*s[2])…….(s[1]*s[n])
. . .
. . .
. . .
. . .
(s[n]*s[1]) (s[n]*s[2])……(s[n]*s[n])
然后我就想如何表示左上角(l,i)到右下角(k,j)的矩阵的和,然后发现每列和每行都有相同的,可以用乘法分配率。如果给一开始的序列打一个前缀和a,那么和就是

(a[j]−a[i−1])∗(a[k]−a[l−1])=A

如果有两个式子相乘,要知道符合的有多少个,那就很容易了。
首先我们枚举第i列到第j列,我们一开始用O(n2)的速度把所有的子序段和(a[k]-a[l-1])存在一个数组hash里面,记录他出现过多少次。那么我们的ans只用加上
aa[j]−a[i−1]就行了。
但是只会有90分,因为如果A=0就会出错。那么我们特判,如果当前搜到第i列到第j列的和为0,然后答案加上所有子序段的个数(也就是i列到j列里所有的矩阵)就可以了(因为0*任何数=0)。
比赛时预计100,实际100,感觉不错,但是想了久了一点。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=5000;
const int mo=1000007;
long long i,j,k,l,t,m,a[maxn],b[maxn];
long long hash[mo],ber,n,ans;
char s[maxn];
int main(){
scanf("%lld",&n);
scanf("%s",s+1);
m=strlen(s+1);
fo(i,1,m){
b[i]=s[i]-'0';
a[i]=a[i-1]+b[i];
}
fo(i,1,m){
fo(j,i,m){
hash[a[j]-a[i-1]]+=1,ber+=1;
}
}
fo(i,1,m){
fo(j,i,m){
t=a[j]-a[i-1];
if(t==0&&(n!=0))continue;
if(t==0&&n==0){
ans+=ber;
}
else if(n%t==0){
if(n/t>1000000)continue;
ans+=hash[n/t];
}
}
}
printf("%lld\n",ans);
}

Problem 2最大子矩阵

Description

我们将矩阵A中位于第i行第j列的元素记作A[i,j]。一个矩阵A是酷的仅当它满足下面的条件:
A[1,1]+A[r,s]<=A[1,s]+A[r,1] (r,s>1)
其中r为矩阵A的行数,s为矩阵A的列数。
进一步,如果一个矩阵是非常酷的仅当它的每一个至少包含两行两列子矩阵都是酷的。
你的任务是,求出一个矩阵A中的一个非常酷的子矩阵B,使得B包含最多元素。

Solution

假设一个酷矩阵的四个角是
a b
c d
所以a+d<=b+c
另一个相邻的酷矩阵四个角是
b e
d f
所以b+f<=e+d
然后两个式子相加就是
a+d+b+f<=b+c+e+d
化简后就是a+f<=c+e
由此可以得出结论任意两个相邻的酷矩阵都可以合成一个大的酷矩阵
那么这题就可以转换一下了:
我们把一个2*2的酷矩阵赋值为1,否则赋值为0,那么就会有一个(n-1) *(m-1)的01矩阵b,现在我们就转换成求一个最大矩阵覆盖。
1、你可以打2*m个单调栈,有点麻烦,我没打
2、然后我就在比赛时推出了个DP,到了i行,设当前这个点j,zuo[j]=从j向左的连续的1的最大长度,you[j]=从j向右的连续的1的最大长度,如果b[i][j]=0,那么zuo[j]=you[j]=0。设f[j]为j能向上最大长度的能往左延伸的长度(其实就是以i,j为一个全是1的矩阵的右下角,并且高度必须是能达到的最高),g[j]就是往右的,同理;还有一个z[j]就是扩展出的矩形是以i这行结尾并包括第j列的最高的矩形高度。因为明显第i行只会与第i-1行有关,那么每次f[j]=min(f[j],zuo[j]),g[j]=min(g[j],you[j]),z[j]++就可以了,但是如果b[i][j]=0,f,g,z就归为初始值。
为什么这样可以。很显然,因为有很多个j,而且每个求得的矩阵都是已经不能再高的矩阵了,自然会概括所有的情况,min的部分相当于,单调栈的小号一直换顶部,这样就好理解了。
看起来很难理解的样子,因为不画图说起来有些复杂,看看Code就好了。
比赛时预计100,实际100,在结束前50秒才调完,惊心动魄。

Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=1005;
int i,j,k,l,t,n,m,ans;
int a[maxn][maxn],f[maxn],g[maxn],b[maxn][maxn];
int z[maxn];
int zuo[maxn],you[maxn];
bool pan(int x,int y){
if(a[x][y]+a[x-1][y-1]<=a[x-1][y]+a[x][y-1])return 1;
return 0;
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n){
fo(j,1,m){
scanf("%d",&a[i][j]);
}
}
fo(i,2,n){
fo(j,2,m){
if(pan(i,j)){
b[i-1][j-1]=1;
}
}
}
memset(f,127,sizeof(f));
memset(g,127,sizeof(g));
fo(i,1,m)z[i]=0;
fo(i,1,n-1){
fo(j,1,m-1){
if(b[i][j]==1)zuo[j]=zuo[j-1]+1;else zuo[j]=0;
}
fod(j,m-1,1){
if(b[i][j]==1)you[j]=you[j+1]+1;else you[j]=0;
}
fo(j,1,m-1){
if(b[i][j]){
f[j]=min(zuo[j],f[j]);
g[j]=min(you[j],g[j]);
z[j]++;
ans=max((z[j]+1)*(f[j]+g[j]),ans);
} else z[j]=0,f[j]=g[j]=0x7fffffff;
}
}
printf("%d",ans);
}

Problem 3最小代价

Description

给你一些黑点和白点,他们组成了一个无向图,要你保留长度和最少的边,使得每个黑点到白点的最短距离不变。输出保留边的长度和。

Solution

比赛时没有怎么想,时间全部都用到了前面去了,但是后来才发现是一道大水题。
打一个MST就好了。
我还以为要打网络流呢,呵呵,其实想复杂了。
首先我们想,如果剩下的是边都有价值,而且是最短距离的一部分,那么很显然是一棵树了,而且边权和最小,那肯定是最小生成树。
但是一个无向图怎么做呢,直接做肯定不满足正确性,我们来考虑如何使最短距离不变。不就是把所有的最短距离求一便,然后保留边就可以了吗。但是直接spfa会计算大量的重边,那么我们可以一开始把黑点或者白点串在一起。我的做法是,新加一个点0,向所有的黑点连一条长度为0的边,这些边就不会做出贡献了。
现在构完图之后,为什么不能直接做最小生成树呢?
这个显然不成立,假如s连向u有一条长度为20的边,但是还有另一条路径就是有30条长度为1的边依次串起来,求最小生成树是先加这30条边,但是这样就不是s到u的最短路径了。
所以,必须要先建一个最短路径图,然后在求最小生成树,这才是最优解。

Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define ll long long
using namespace std;
const int maxn=100005;
ll i,j,k,l,t,n,m,ans,num;
ll biao[maxn*5],first[maxn*8],next[maxn*8],last[maxn*8],chang[maxn*8],data[maxn*8];
ll d[maxn*8];int head,tail,tot,f[maxn*5],ans1,a[maxn*8];
bool bz[maxn*5],az[maxn*5];
struct node{
int a,b,c;
}tree[maxn*20];
bool cmp(node x,node y){return x.c<y.c;}
void add(int x,int y,int z){
last[++num]=y;
next[num]=first[x];
first[x]=num;
chang[num]=z;
last[++num]=x;
next[num]=first[y];
first[y]=num;
chang[num]=z;
}
void spfa(int x){
ll i,j,k,now;
memset(d,127,sizeof(d));
d[0]=0;
data[++tail]=0;
bz[0]=1;
while(head<tail){
now=data[++head];
for(i=first[now];i;i=next[i]){
if(d[now]+chang[i]<d[last[i]]){
d[last[i]]=d[now]+chang[i];
if(!bz[last[i]]){
data[++tail]=last[i];
bz[last[i]]=1;
}
}
}
bz[now]=0;
}
}
int gf(int x){
if(f[x]==x)return x;
else return f[x]=gf(f[x]);
}
int main(){
scanf("%lld%lld",&n,&m);
fo(i,1,n){
scanf("%lld",&a[i]);
if(a[i]==1){
add(0,i,0);
}
}
fo(i,1,m){
scanf("%lld%lld%lld",&k,&l,&t);
add(k,l,t);
}
spfa(0);
ll u=0;
fo(i,0,n){
for(j=first[i];j;j=next[j]){
if(chang[j]+d[last[j]]==d[i]){
tree[++tot].a=i;
tree[tot].b=last[j];
tree[tot].c=chang[j];
az[i]=az[last[j]]=1;
}
}
if(az[i])u++;
}
sort(tree+1,tree+tot+1,cmp);
fo(i,0,n)f[i]=i;
fo(i,1,tot){
k=gf(tree[i].a);l=gf(tree[i].b);
if(k!=l){
f[l]=k;
ans1++;
ans+=tree[i].c;
}
if(ans1==u-1) break;
}
if(ans==0) printf("impossible");
else printf("%lld\n",ans);
}

Problem 4WTF交换

Description

假定给出一个包含N个整数的数组A,包含N+1个整数的数组ID,与整数R。其中ID数组中的整数均在区间[1,N-1]中。
用下面的算法对A进行Warshall-Turing-Fourier变换(WTF):
sum = 0
for i = 1 to N
index = min{ ID[i], ID[i+1] }
sum = sum + A[index]
将数组A往右循环移动R位
将数组A内所有的数取相反数
for i = 1 to N
index = max{ ID[i], ID[i+1] }
index = index + 1
sum = sum + A[index]
将数组A往右循环移动R位
给出数组A以及整数R,但没有给出数组ID。在对数组A进行了WTF算法后,变量sum的可能出现的最大值数多少?

Solution

第三题都没时间打,那么第四题这种长得那么鬼畜的题肯定没时间打了。
然而其实60分的Dp是这几道题里面最短的程序。
因为当前在ID[i]填的这个数贡献的答案只与ID[i+1]有关,那么显然可以Dp。
设f[i][j]为当前ID[i]填的数为j,那么显然f[i][j]=max(f[i−1][k]+((a[min(k,j)−(i−1)∗r)modn])−((a[max(k,j)−(i−1)∗r+1])modn))
然后再用一个g[i][j]统计当前ID[i]填j是有f[i−1][g[i][j]]转移过来的,最后就能得出答案,然后60分就到手了。
因为是取最大和最小,所以有一个地方是固定的。需要分类(k < j;k = j;k > j)。
求一个mx[i][j]表示f[i−1][mx[i][j]]+((a[mx[i][j]−(i−1)∗r+1])modn))的最大值mx[i][j]表示它的位置,mi[i][j]是小的,同理。

Code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=3005;
int i,j,k,l,t,n,m,ans,a[maxn],ans1[maxn],yi,er;
int f[maxn][maxn],g[maxn][maxn];
int mi[maxn][maxn],mx[maxn][maxn];
int doing(int x,int y){
int o=((x-y*m)+n*y)%n;
if(o==0)o=n;
return o;
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n){
scanf("%d",&a[i]);
}
mx[0][1]=1;
mi[0][n-1]=n-1;
fo(i,2,n-1){
if (a[i]>a[mx[0][i-1]])mx[0][i]=i;
else mx[0][i]=mx[0][i-1];
}
fod(i,n-2,1){
if(a[i+1]<a[mi[0][i+1]+1])mi[0][i]=i;
else mi[0][i]=mi[0][i+1];
}
fo(i,1,n){
fo(j,1,n-1){
yi=doing(j,i-1);
er=doing(j+1,i-1);
f[i][j]=f[i-1][j]+a[yi]-a[er];
g[i][j]=j;
if (j>=2) {
k=mx[i-1][j-1];
l=doing(k,i-1);
if (f[i-1][k]+a[l]-a[er]>f[i][j]) {
f[i][j]=f[i-1][k]+a[l]-a[er];
g[i][j]=k;
}
}
if (j<=n-2) {
k=mi[i-1][j+1];
l=doing(k+1,i-1);
if (f[i-1][k]+a[yi]-a[l]>f[i][j]) {
f[i][j]=f[i-1][k]+a[yi]-a[l];
g[i][j]=k;
}
}
}
mx[i][1]=1;
mi[i][n-1]=n-1;
fo(j,2,n-1){
if (f[i][j]+a[doing(j,i)]>f[i][mx[i][j-1]]+a[doing(mx[i][j-1],i)])mx[i][j]=j;
else mx[i][j]=mx[i][j-1];
}
fod(j,n-2,1){
if (f[i][j]-a[doing(j+1,i)]>f[i][mi[i][j+1]]-a[doing(mi[i][j+1]+1,i)])mi[i][j]=j;
else mi[i][j]=mi[i][j+1];
}
}
ans=-0x7fffffff;
fo(i,1,n-1){
if(f[n][i]>ans){
ans=f[n][i];
j=i;
}
}
printf("%d\n",ans);
ans1[n+1]=j;
fod(i,n,1)ans1[i]=g[i][ans1[i+1]];
fo(i,1,n+1) printf("%d ",ans1[i]);
}