A.哥德巴赫矩阵
问题描述
根据哥德巴赫猜想(每个不小于 6 的偶数都可以表示为两个奇素数之和),定义
哥德巴赫矩阵 A 如下:对于正整数对(i,j),若 i+j 为偶数且 i,j 均为奇素数,则 Ai,j = 1,
否则 Ai,j = 0。现在有若干询问(x1,y1,x2,y2),你需要回答下列式子的值
输入
第一行一个整数 m
接下来 m 行,每行四个整数 x1 y1 x2 y2,表示一个询问
输出
m 行,每行一个整数,表示对应询问的答案
输入样例
1
1 1 3 5
输出样例
2
数据规模与约定
30%的数据保证 x2, y2, m ≤ 100
100%的数据保证 1 ≤ x1 ≤ x2 ≤ 106; 1 ≤ y1 ≤ y2 ≤ 106; m ≤ 1000
题解
比赛时没有预处理前缀和,也没开long long(比赛经验太差)
#include<cstdio>
#include<cstring>
typedef long long ll;
const int N=1e6;
int prime[N+10],p=0;
bool iscomp[N+10];
void primetable()
{
for(int i=2;i<=N;i++)
{
if(!iscomp[i])prime[p++]=i;
for(int j=0;j<p&&i*prime[j]<=N;j++)
{
iscomp[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
ll sum[N+10];
void Init()
{
for(int i=1;i<=N;i++)
{
if(i==1||i==2||iscomp[i])sum[i]=sum[i-1];
else sum[i]=sum[i-1]+1;
}
}
int main()
{
//freopen("pmatrix.in","r",stdin);
//freopen("pmatrix.out","w",stdout);
int m,x1,y1,x2,y2,i,j;
ll ans;
scanf("%d",&m);
memset(iscomp,0,sizeof(iscomp));
primetable();
Init();
for(int k=1;k<=m;k++)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
ans=(sum[x2]-sum[x1-1])*(sum[y2]-sum[y1-1]);
printf("%lld\n",ans);
}
return 0;
}
总结
感觉是这次比赛唯一能做的题……错误的认为应该跑得过,于是没有用前缀和;也没有想过会不会爆int
主要反映出比赛经验的严重不足
B.表格
问题描述
给出一个表格, N 行 M 列,每个格子有一个整数,有些格子是空的。现在需要你
来做出一些调整,使得每行都是非降序的。这个调整只能是整列的移动。
输入
第一行两个正整数 N 和 M。
接下来 N 行,每行 M 个整数, -1 表示这个格子是空的,其他的整数都在 [0, 10^9]范围,表
示格子的数字。
输出
若无解,输出 -1;
否则输出任意一个解,即一行 M 个正整数 p1, p2, · · · , pm,表示可以把初始表格的 pi 列,
放在新表格的第 i 列,以得到一个合法的表格。
样例输入 1
3 3
-1 -1 -1
2 1 2
2 -1 1
样例输出 1
2 3 1
样例输入 2
2 2
1 2
2 1
样例输出 2
1
数据规模与约定
对于 20% 的数据,满足 1 ≤ N ≤ 8, 1 ≤ M ≤ 8。
对于 60% 的数据,满足 1 ≤ N × M ≤ 2 × 10^3。
对于 100% 的数据,满足 1 ≤ N × M ≤ 10^5。
题解
对每一列小到大排序,忽略-1 的。排前面的列,向后面的建边。因为有多个相同数字的,
所以建一些虚点。
如
2 2 3 3
在 2,3 之间建一个虚点, 2 2 向虚点建边,虚点向 3 3 建边
最后跑拓扑排序
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#define PB(v) push_back(v)
using namespace std;
const int N=5e5+10;
int n,m;//n行m列
struct node{
int num,idy;//数值和初始列序号
bool operator <(const node&rhs)const{
return num<rhs.num;}
}table[N];
vector<int>vx[N];//建图用
queue<int>q;//存所有入度为0的点
int deg[N];//单行中每列的入度
int ans[N];//记录可行解
int main()
{
freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);//n行m列
int xd=m;//虚点下标
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)//读入1行中每列的数值
{
scanf("%d",&table[j].num);
table[j].idy=j;
}
sort(table,table+m);//对一行的值进行排序
for(int k=-1,l=0,r=0;l<m;l=r)
{
//k为建立的虚点,l为相同区间左端点,r为右端点
while(r<m&&table[r].num==table[l].num)r++;
if(table[l].num==-1)continue;
if(k!=-1)for(int o=l;o<r;o++)vx[k].PB(table[o].idy),deg[table[o].idy]++;
//虚点已经建立,则虚点向后连边
k=xd++;//新建虚点
for(int o=l;o<r;o++)vx[table[o].idy].PB(k),deg[k]++;//将之前的点向虚点连边
}
}
int top=0;
for(int i=0;i<xd;i++)
if(!deg[i])q.push(i);
while(!q.empty())//拓扑排序
{
int u=q.front();
q.pop();
if(u<m)ans[top++]=u;
for(int i=0;i<vx[u].size();i++)
{
int v=vx[u][i];
if(--deg[v]==0)q.push(v);
}
}
if(top<m)puts("-1");
else for(int i=0;i<m;i++)printf("%d%c",ans[i]+1,i==m-1?'\n':' ');
return 0;
}
总结
比赛时完全没思路……对拓扑排序很不熟
比赛时拿到这题完全不知道怎么做,严格升序与不严格升序看得头大。
反映出对于拓扑排序这个知识点的模糊
trick:通过建虚点优化
C.看电影
问题描述
小石头喜欢看电影,选择有 N 部电影可供选择,每一部电影会在一天的不同时段播
放。他希望连续看 L 分钟的电影。因为电影院是他家开的,所以他可以在一部电影播放过
程中任何时间进入或退出,当然他不希望重复看一部电影,所以每部电影他最多看一次,
也不能在看一部电影的时候,换到另一个正在播放一样电影的放映厅。
请你帮助小石头让他重 0 到 L 连续不断的看电影,如果可以的话,计算出最少看几
部电影。
输入格式
第一行是 2 个整数 N,L,表示电影的数量,和小石头希望看的连续时间
接下来是 N 行,每行第一个整数 D(1<=D<=L)表示电影播放一次的播放时间,第二个整数
是 C 表示这部电影有 C 次播放,接下来是 C 个整数表示 C 次播放的开始时间 Ti
(0<=Ti<=L) ,Ti 是按升序给出。
输出格式
一个整数,表示小石头最少看的电影数量,如果不能完成输出-1
输入样例
4 100
50 3 15 30 55
40 2 0 65
30 2 20 90
20 1 0
输出样例
3
样例说明
开始他选择最后一步电影从 0 时间开始。
到了 20 分钟,他选择第一部电影的第一次播放,看到 65 分钟
最后他选择第二部电影的第二次播放,从 65 分钟到 100 分钟
数据规模
30%数据 N<=10
100%数据 N<=20, 1 <= L <= 100,000,000
题解
状压 DP, f[i]表示状态为 i 时从 0 最远连续看到哪。
然后转移时候, 枚举还要看哪部电影,贪心取能看的片场中最靠后的一个。
然后时间复杂度 O(2^N×N×x)
其中 x 是求能看片场中最靠后一个的时间复杂度。
求法 1:
二分。 x=log2C
求法 2:
类似单调队列预处理,然后 x=1
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int N=25;
int n,l;
int S;
int len[N],movie[N][1010];
int f[1<<N];//f[i]表示状态为i时能看的最长时间
int p[N];
int find(int x,int id)
{
int l=-1,r=p[id]-1,mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(movie[id][mid]<=x)l=mid;
else r=mid-1;
}
return l;
}
int main()
{
//freopen("in.txt","r",stdin);
int i,j,k,ans=INF;
scanf("%d%d",&n,&l);
S=1<<n;
for(i=0;i<n;i++)
{
scanf("%d%d",&len[i],&p[i]);
for(j=0;j<p[i];j++)scanf("%d",&movie[i][j]);
}
memset(f,-1,sizeof(f));
f[0]=0;
for(i=0;i<S;i++)
{
if(f[i]==-1)continue;
if(f[i]>=l)
{
for(j=0,k=i;k;k-=k&(-k))j++;
ans=min(ans,j);
continue;
}
for(j=0;j<n;j++)
{
if(i&(1<<j))continue;
k=find(f[i],j);
if(k==-1)continue;
f[i|(1<<j)]=max(f[i|1<<j],movie[j][k]+len[j]);
}
}
if(ans==INF)puts("-1");
else printf("%d\n",ans);
return 0;
}
总结
比赛时看数据规模感觉是状压DP来着……但是写不来
比赛时看到n<=20的数据规模就感觉是状压DP,但是却不知道怎么写最后放弃了,暴力也不成功。
反映出代码能力不足和对于状压DP的生疏
赛后总结
打OI这么久第一次参加的现场比赛,看到这些题毫无心理准备,感觉和自己以前写的题是两个概念。尤其是听到其他大佬敲得飞快时就更慌了。成绩出来我这个速成选手直接被打回原形,感觉之前的一切YY都破灭了,也对自己的菜有了充分的认识。恶补算法1个月上了14节课,就想挑翻那些高手,也太天真了,好歹别人系统训练一年了。但都到这份上了,还有啥好说的呢,只要还没出局,我会一直进攻。