做题感受:
410 / 500
前三道题目都比较简单,很快就能写完;T4 没想到超级源点,写了一个朴素BFS拿了90分;T5 不是很会,骗分拿了20分。总的来说T3相对较水,所以做的很顺。
一些需要注意的点:
1、能使用结构体或者数组,就尽量不要使用vector
2、当读入的数据量超过 1e6 就要考虑 使用scanf/printf 而不是 cin/cout
T4因为使用cin在AcWing上一直TLE
T5一开始使用vector写矩阵快速幂(我存的板子),结果导致TLE;并且T4在存储询问数据的结构选择上,使用结构体存储比使用vector<Node>存要快 50ms 左右
主要收获:
题目编号 | 题目名称 | 知识点 |
---|---|---|
T4 | 201403-4 无线网络 | 多源最短路问题 |
T5 | 201403-5 任务调度 | 状压DP + 矩阵快速幂 |
201409-1 相邻数对
水题,排个序统计一下就能得到答案:
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn=1e4+5;
int n,x,a[maxn],ans;
int main()
{
for(cin>>n;n;n--){
cin>>x;
a[x]++;
}
for(int i=0;i<10000;i++)
if(a[i]&&a[i+1]) ans++;
cout<<ans<<'\n';
return 0;
}
201409-2 画图
数据范围比较小,所以也是简单题,直接暴力模拟涂色即可,复杂度
O
(
n
3
)
O(n^3)
O(n3)
代码:
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn=105;
int n,a[maxn][maxn],x1,y1,x2,y2,ans;
int main()
{
cin>>n;
while(n--){
cin>>x1>>y1>>x2>>y2;
for(int i=x1+1;i<=x2;i++)
for(int j=y1+1;j<=y2;j++)
a[i][j]++;
}
for(int i=1;i<maxn;i++)
for(int j=1;j<maxn;j++)
if(a[i][j]) ans++;
cout<<ans<<'\n';
return 0;
}
如果题目的数据范围加强的话 就变成一道 T5 了 ,需要用到 线段树 扫描线 来做,复杂度可以降为
n
log
(
n
)
n\log(n)
nlog(n)。
201409-3 字符串匹配
数据范围小,简单题。这里通过 string中自带的find()函数来查找模式串。
若没找到则返回 npos (通过判断是不是 -1 好像也可)
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
bool search(string pa,string s,int ops){
if(!ops){
for(int i=0;i<s.size();i++)
if(s[i]<'a') s[i]=s[i]+32;
for(int i=0;i<pa.size();i++)
if(pa[i]<'a') pa[i]=pa[i]+32;
}
return s.find(pa)!=s.npos;
}
int main()
{
string s,pattern;
int ops,n;
cin>>pattern>>ops>>n;
while(n--){
cin>>s;
if(search(pattern,s,ops))
cout<<s<<'\n';
}
return 0;
}
补充说几点:
-
find() 函数是通过暴力查找匹配,但是在一般题目(不专门考察字符串匹配,样例不卡算法)中效率很高。
-
strstr()函数可以进行字符数组的匹配,闫老师说和KMP效率一样,但是查了很多资料后持怀疑态度,没有真正拿题目试过,也没有专门测试过二者的效率。
附:strstr() 解析1 \quad strstr() 解析2
201409-4 最优配餐
读完题目稍加分析,就可以发现这是一道多源最短路问题(注意和我们通常使用floyd求解的多源汇最短路问题进行区分)。
解决这类问题的一个常见技巧是建立一个虚拟的超级源点,然后建立超级源点到每个起点的虚拟边,并令每条边的权值为0。最后在这个新的图上求解单源最短路即可得到答案。
这道题目由于每条边的权重都是1,因而退化为使用BFS求解;并且可以不用真的建立虚拟源点,而是直接将所有源点直接插入队列,这样代码更加简洁。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1e3+5;
bool obs[maxn][maxn],vis[maxn][maxn];
int n,m,k,d;
int dis[maxn][maxn];
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
struct Node{ int x,y,w; };
void BFS(int sx,int sy){
memset(vis,0,sizeof vis);
queue<PII> q;
q.push(PII(sx,sy));
dis[sx][sy]=0;
while(!q.empty()){
auto u=q.front();
q.pop();
if(vis[u.first][u.second]) continue;
vis[u.first][u.second]=1;
for(int i=0;i<4;i++){
int xx=u.first+dx[i], yy=u.second+dy[i];
if(xx<1||xx>n||yy<1||yy>n||obs[xx][yy]) continue;
if(dis[xx][yy]>dis[u.first][u.second]+1){
dis[xx][yy]=dis[u.first][u.second]+1;
q.push(PII(xx,yy));
}
}
}
return;
}
int main()
{
cin>>n>>m>>k>>d;
vector<PII> src;
vector<Node> dst;
int x,y,w;
for(int i=1;i<=m;i++){
cin>>x>>y;
src.push_back(PII(x,y));
}
for(int i=1;i<=k;i++){
cin>>x>>y>>w;
dst.push_back(Node{x,y,w});
}
while(d--){
cin>>x>>y;
obs[x][y]=true;
}
memset(dis,0x3f,sizeof dis);
for(auto u:src) BFS(u.first,u.second);
ll res=0;
for(auto u:dst){
res+=(ll)dis[u.x][u.y]*u.w;
}
cout<<res<<'\n';
return 0;
}
这里附一道很久之前写过的,通过建立超级源点,将问题转化为最小生成树的思维好题: Codeforces 1245D
201409-5 拼图
这是一道糅合了 状态压缩DP题、快速幂、矩阵乘法 共3个知识点的题目。
用
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示放完前
i
i
i 行,且前
i
i
i 行已被占用的格子的状态为
j
j
j 的所有方案数,这里
0
<
i
≤
n
0<i\leq n
0<i≤n,
0
≤
j
<
2
m
0\leq j < 2^m
0≤j<2m.
这样状态转移可以用DFS,求出当前行的每个状态到填满该行后下一行各个状态的方案数,存入矩阵 c c c 中,这样从 f [ i ] → f [ i + 1 ] f[i] \rightarrow f[i + 1] f[i]→f[i+1] 就相当于乘了一个以方案数为权值的矩阵 c c c.
所以 f [ n ] = f [ 0 ] × c n f[n] = f[0] \times c^n f[n]=f[0]×cn,又因为 f [ 0 ] f[0] f[0]只有 f [ 0 ] [ ( 1 << m ) − 1 ] f[0][(1\texttt{<<}m)-1] f[0][(1<<m)−1]是1,其余值都为0,所以直接输出 c n [ ( 1 << m ) − 1 ] [ ( 1 << m ) − 1 ] c^n[(1\texttt{<<}m)-1][(1\texttt{<<}m)-1] cn[(1<<m)−1][(1<<m)−1]就是答案,当然这里数据范围很大,需要通过快速幂优化。
时间复杂度: O ( ( 2 m ) 3 log n ) O((2^m)^3\log n) O((2m)3logn)
代码:
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=128+5;
ll n;
int m,c[maxn][maxn];
void mul(int c[][maxn], int a[][maxn], int b[][maxn]){
static int tmp[maxn][maxn];
memset(tmp, 0, sizeof tmp);
for (int i = 0; i < 1 << m; i ++ )
for (int j = 0; j < 1 << m; j ++ )
for (int k = 0; k < 1 << m; k ++ )
tmp[i][j] = (tmp[i][j] + (ll)a[i][k] * b[k][j]) % mod;
memcpy(c, tmp, sizeof tmp);
}
//当前行状态是cur 下一行状态是nxt 从第s位开始搜
void DFS(int cur,int nxt,int s){
if(s==m) c[cur][nxt]++;
else if(cur>>s&1) DFS(cur,nxt,s+1);
else{
if(s && !(nxt>>s&1) && !(nxt>>s-1&1))
DFS(cur, nxt+(1<<s)+(1<<s-1), s+1);
if(s<m-1 && !(nxt>>s&1) && !(nxt>>s+1&1))
DFS(cur, nxt+(1<<s)+(1<<s+1), s+1);
if(s<m-1 && !(cur>>s+1&1)){
if(!(nxt>>s&1)) DFS(cur, nxt+(1<<s), s+2);
if(!(nxt>>s+1&1)) DFS(cur, nxt+(1<<s+1), s+2);
}
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<(1<<m);i++)
DFS(i,0,0);
int res[maxn][maxn]={0};
for(int i=0;i<1<<m;i++) res[i][i]=1;
while(n>0){
if(n&1) mul(res,res,c);
mul(c,c,c);
n>>=1;
}
cout<<res[(1<<m)-1][(1<<m)-1]<<'\n';
return 0;
}
附两道类似的状压DP题目以便练习:
蒙德里安的梦想
最短Hamilton路径
另附 快速幂板子