2021.02.18 北师大寒假新生训练
Candies and Two Sisters
- 签到
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main() {
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
if (n & 1) printf("%d\n", n / 2);
else printf("%d\n", n / 2 - 1);
}
return 0;
}
Construct the String
- 从’a’ 到 ‘a’ + b 循环往里面填字母就好。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2010;
char s[maxn];
int main() {
int T;
scanf("%d", &T);
while (T--) {
int n, a, b;
scanf("%d%d%d", &n, &a, &b);
for (int i = 0; i < n; i++) {
s[i] = 'a' + i % b;
}
s[n] = 0;
printf("%s\n", s);
}
return 0;
}
Two Teams Composing
- 分类讨论题
{ t y p e , t y p e ≤ m − 1 ; m − 1 , t y p e = m ; m , t y p e > m . \begin{cases}type, \quad type \le m-1; \\ m-1, \quad type = m; \\ m, \quad type > m.\end{cases} ⎩⎪⎨⎪⎧type,type≤m−1;m−1,type=m;m,type>m.
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
unordered_map<int, int> um;
int main() {
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
um.clear();
int m = 0;
for (int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
um[x]++;
m = max(um[x], m);
}
int type = um.size();
if (type <= m - 1) printf("%d\n", type);
else if (type == m) printf("%d\n", m - 1);
else printf("%d\n", m);
}
return 0;
}
Anti-Sudoku
- 依次挑选小矩阵中的元素,比如挑选 第(i,j) 的小矩形,那么就挑选小矩形中的 (j,i) 元素,只要修改的和之前的不一样,那么一定就会满足条件,因为该行该列该小矩阵都是只有这一个元素被修改掉。
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
char mz[15][15];
int main() {
int T;
scanf("%d", &T);
while (T--) {
for (int i = 1; i <= 9; i++) scanf("%s", mz[i] + 1);
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
int x = 3 * (j - 1) + i, y = 3 * (i - 1) + j;
if (mz[x][y] == '1') mz[x][y] = '2';
else mz[x][y] = '1';
}
}
for (int i = 1; i <= 9; i++) printf("%s\n", mz[i] + 1);
}
return 0;
}
Three Blocks Palindrome (hard version)
- 我们发现a的范围很小,于是我们可以先确定第一个数字,然后用双指针确定x的值,那么中间y的值就是两边对称的x,所夹区间出现次数最多的数字是多少。
- 那么难点就在于,我们能在 O ( 1 ) O(1) O(1) 求数组中某个区间,某个数字出现的次数。
- 其实可以用前缀和,开一个数组 a [ 200010 ] [ 210 ] a[200010][210] a[200010][210], a [ i ] [ j ] a[i][j] a[i][j] 记录数字 j j j 在第 i 个位置是否出现。出现的话为1,没有出现为0。然后求这个数字的前缀和。这样子,就解决了上述那个问题。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 200010, maxa = 210;
int a[maxn][maxa];
vector<int> ids[maxa];
int main() {
int T;
scanf("%d", &T);
while (T--) {
int N;
scanf("%d", &N);
for (int i = 1; i <= 200; i++) ids[i].clear();
for (int i = 1; i <= N; i++) {
memset(a[i], 0, sizeof a[i]);
int x;
scanf("%d", &x);
a[i][x] = 1;
ids[x].push_back(i);
}
int maxsz = 0;
for (int j = 1; j <= 200; j++) {
maxsz = max(maxsz, (int)ids[j].size());
for (int i = 1; i <= N; i++) {
a[i][j] += a[i - 1][j];
}
}
int ans = maxsz;
for (int m = 1; m <= 200; m++) {
if ((int)ids[m].size() <= 1) continue;
int sz = (int)ids[m].size();
int l = 0, r = sz - 1;
while (r > l) {
int x = l + 1;
int y = 0;
for (int i = 1; i <= 200; i++) {
y = max(y, a[ids[m][r] - 1][i] - a[ids[m][l]][i]);
//if (i == 1) printf("### %d\n", a[ids[m][r] - 1][i] - a[ids[m][l]][i]);
}
ans = max(2 * x + y, ans);
l++, r--;
}
}
printf("%d\n", ans);
}
}
Robots on a Grid
- 图论(队友写的)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
string dir[1000010],color[1000010];
int occupy[1000010];
int p[1000010][30],ans1,ans2;
void init()
{
ans1=ans2=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x=(i-1)*m+j;
occupy[x]=-1;
for(int k=0;k<=25;k++)
p[x][k]=0;
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
int u=(i-1)*m+j+1;
if(dir[i][j]=='U')
{
p[u][0]=u-m;
}
else if(dir[i][j]=='D')
{
p[u][0]=u+m;
}
else if(dir[i][j]=='L')
{
p[u][0]=u-1;
}
else if(dir[i][j]=='R')
{
p[u][0]=u+1;
}
}
}
for(int j=1;(1<<j)<=n*m;j++)
{
for(int i=1;i<=n*m;i++)
{
p[i][j]=p[p[i][j-1]][j-1];
}
}
}
void solve()
{
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
int c=color[i][j]-'0',u=(i-1)*m+j+1;
for(int k=(int)log2(n*m);k>=0;k--)
{
if(!p[u][k])
continue;
u=p[u][k];
}
if(occupy[u]!=0)
occupy[u]=c;
}
}
for(int i=1;i<=n*m;i++)
{
if(occupy[i]!=-1)
ans1++;
if(occupy[i]==0)
ans2++;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
cin>>color[i];
}
for(int i=1;i<=n;i++)
{
cin>>dir[i];
}
init();
solve();
printf("%d %d\n",ans1,ans2);
}
return 0;
}
Decimal
- 找找规律会发现,把n质因数分解后,只要分解的结果,含有除2或5以外其他质因数,那么就是无限小数。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
while (n % 2 == 0) n /= 2;
while (n % 5 == 0) n /= 5;
if (n == 1) printf("No\n");
else printf("Yes\n");
}
return 0;
}
Forest Program
- 图论
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll vis[300010],pre[300010],sum;
ll h[1000010],ne[1000010],e[1000010],idx;
ll ans=1;
ll qmi(ll m,ll k)
{
ll res=1,t=m;
while(k)
{
if(k&1)
res=res*t%mod;
t=t*t%mod;
k>>=1;
}
return res;
}
void add(ll a,ll b)
{
e[++idx]=b;
ne[idx]=h[a];
h[a]=idx;
}
void dfs(ll u,ll fa)
{
vis[u]=1;
for(ll i=h[u];i;i=ne[i])
{
ll v=e[i];
if(v==fa)
continue;
if(vis[v]==2)
continue;
if(!vis[v])
{
pre[v]=u;
dfs(v,u);
}
else
{
ll step=1;
ll tmp=u;
while(tmp!=v)
{
step++;
tmp=pre[tmp];
}
sum+=step;
ans=ans*(qmi(2,step)-1)%mod;
}
}
vis[u]=2;
}
int main()
{
ll n,m;
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++)
{
ll a,b;
scanf("%lld%lld",&a,&b);
add(a,b);
add(b,a);
}
for(ll i=1;i<=n;i++)
{
if(!vis[i])
{
dfs(i,0);
}
}
ll res=m-sum;
ans=ans*qmi(2,res)%mod;
printf("%lld",ans);
return 0;
}
Angle Beats
- 给定一个点,问有多少条点对,可以与这个点构成一个直角三角形。
- 思路很简单,就是非常卡时间。我们分为 A i A_i Ai 是否作为直角边分类讨论。把所有的边存在哈希表中,然后查找。
- 省时间策略:
- 不用map,用unordered_map
- unordered_map里面不要存pair,更不要存三元组,存一维的元素,省空间省时间。具体hash的办法很简单,就是 x * hash_num + y 即可。
- 把线段方向保存为向量(dx, dy),而不是保存倾斜角,防止浮点数误差。dx, dy 都除以最大公约数,用这种方式把它们都标准化。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
#define x first
#define y second
typedef long long ll;
const int maxn = 2010;
const ll hash_num = 3e9 + 9;
typedef pair<int, int> P;
ll Hash(const P& p) {
return hash_num * (ll)p.x + (ll)p.y;
}
P ps[maxn], qs[maxn];
unordered_map<ll, int> tmap;
int ans[maxn];
inline int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
inline void normalize(P& p) {
int dx = p.x, dy = p.y;
int d = gcd(abs(dx), abs(dy));
dy /= d, dx /= d;
p.x = dx, p.y = dy;
}
int main() {
int N, Q;
scanf("%d%d", &N, &Q);
for (int i = 1; i <= N; i++) {
scanf("%d%d", &ps[i].x, &ps[i].y);
}
for (int i = 1; i <= Q; i++) {
scanf("%d%d", &qs[i].x, &qs[i].y);
}
for (int i = 1; i <= Q; i++) {
tmap.clear();
for (int j = 1; j <= N; j++) {
P p(qs[i].x - ps[j].x, qs[i].y - ps[j].y);
normalize(p);
tmap[Hash(p)]++;
}
for (int j = 1; j <= N; j++) {
P p(qs[i].x - ps[j].x, qs[i].y - ps[j].y);
normalize(p);
//两个方向都要加上去
ans[i] += tmap[Hash(P(-p.y, p.x))];
ans[i] += tmap[Hash(P(p.y, -p.x))];
}
ans[i] /= 2;
}
for (int i = 1; i <= N; i++) {
tmap.clear();
for (int j = 1; j <= N; j++) {
if (i != j) {
P p = { ps[i].x - ps[j].x, ps[i].y - ps[j].y };
normalize(p);
tmap[Hash(p)]++;
}
}
for (int j = 1; j <= Q; j++) {
P p = { ps[i].x - qs[j].x, ps[i].y - qs[j].y };
normalize(p);
ans[j] += tmap[Hash(P(-p.y, p.x))];
ans[j] += tmap[Hash(P(p.y, -p.x))];
}
}
for (int i = 1; i <= Q; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
Invoker
- dp问题,感觉应该归为一个状态机的问题。 d p [ i ] [ j ] dp[i][j] dp[i][j] = m i n ( d p [ i ] [ j ] , d p [ i − 1 ] [ k ] + d i s t ( k → j ) min(dp[i][j],dp[i-1][k]+dist(k \rightarrow j) min(dp[i][j],dp[i−1][k]+dist(k→j)。搜索从 i - 1 到 i 状态,从6种字母到另外 6 种字母,的最小值。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
char words[10][6][4] = {
{"QQQ","QQQ","QQQ","QQQ","QQQ","QQQ"},
{"QQW","QWQ","WQQ","WQQ","WQQ","WQQ"},
{"QQE","QEQ","EQQ","EQQ","EQQ","EQQ"},
{"WWW","WWW","WWW","WWW","WWW","WWW"},
{"QWW","WQW","WWQ","WWQ","WWQ","WWQ"},
{"WWE","WEW","EWW","EWW","EWW","EWW"},
{"EEE","EEE","EEE","EEE","EEE","EEE"},
{"QEE","EQE","EEQ","EEQ","EEQ","EEQ"},
{"WEE","EWE","EEW","EEW","EEW","EEW"},
{"QWE","QEW","EQW","EWQ","WEQ","WQE"},
};
const int maxn = 100010;
char str[maxn], p[maxn];
int f[maxn][6];
unordered_map<char, int> um;
int dist(int a, int b, int x, int y) {
//计算从 words[a][b] 到 words[x][y] 需要在末尾添加几个字母,即用几次技能。
if (words[a][b][0] == words[x][y][0] && words[a][b][1] == words[x][y][1] && words[a][b][2] == words[x][y][2]) {
return 0;
}
if (words[a][b][1] == words[x][y][0] && words[a][b][2] == words[x][y][1]) {
return 1;
}
if (words[a][b][2] == words[x][y][0]) {
return 2;
}
return 3;
}
int main() {
um['X'] = 0; um['V'] = 1; um['G'] = 2;
um['C'] = 3; um['X'] = 4; um['Z'] = 5;
um['T'] = 6; um['F'] = 7; um['D'] = 8; um['B'] = 9;
scanf("%s", str);
//前后两个字母相同只需要按R,把这些东西先挑出来。
int sz = 0, res1 = strlen(str);
p[sz++] = um[str[0]];
for (int i = 1; str[i]; i++) {
if (str[i] != str[i - 1]) p[sz++] = um[str[i]];
}
//for (int i = 0; i < sz; i++) {
// printf("* %d\n", p[i]);
//}
//printf("%d\n", res1);
memset(f, 0x3f, sizeof f);
for (int j = 0; j < 6; j++) f[0][j] = 3;
for (int i = 1; i < sz; i++) {
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 6; k++) {
f[i][j] = min(f[i][j], f[i - 1][k] + dist(p[i - 1], k, p[i], j));
}
//printf("### %d %d %d\n", i, j, f[i][j]);
}
}
int res2 = 1e9;
for (int j = 0; j < 6; j++) {
res2 = min(res2, f[sz - 1][j]);
}
printf("%d\n", res1 + res2);
return 0;
}
MUV LUV EXTRA
- 字符串,KMP + 最小循环节。我们发现一个规律(假设ne数组和模式串下标均从1开始):最小循环节(题目中的 l)= i - ne[i]. (其实告诉这个规律后,发现道理也挺显然的)
- i 从 1 到 N 一直更新答案就可以了
ans = max(ans, a * i - b * (i - ne[i]))
完整代码:
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 10000010;
int ne[maxn], N;
char str[maxn], p[maxn];
typedef long long ll;
ll a, b;
int main() {
scanf("%lld%lld%s", &a, &b, str);
for (int i = 0; str[i]; i++) {
if (str[i] == '.') {
strcpy(p + 1, str + i + 1);
break;
}
}
N = strlen(p + 1);
reverse(p + 1, p + N + 1);
//计算 ne 数组
for (int i = 2, j = 0; i <= N; i++) {
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j++;
ne[i] = j;
}
//printf("### %s\n", p + 1);
//for (int i = 1; i <= N; i++) printf("*** %d %d\n", i, ne[i]);
ll ans = -1e9;
for (int i = 1; i <= N; i++) {
ll p = i, l = i - ne[i];
ans = max(ans, a * p - b * l);
}
printf("%lld\n", ans);
return 0;
}
MUV LUV UNLIMITED
- 树上博弈
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int fa[1000010],du[1000010],n;
int h[1000010],ne[1000010],e[1000010],idx;
int depth[1000010];
void add(int a,int b)
{
e[++idx]=b;
ne[idx]=h[a];
h[a]=idx;
}
void dfs(int u,int len)
{
if(du[u]>1)
len=0;
depth[u]=len;
for(int i=h[u];i;i=ne[i])
{
int v=e[i];
dfs(v,len+1);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
du[i]=0;
h[i]=0;
depth[i]=0;
}
idx=0;
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
fa[i]=x;
du[x]++;
add(x,i);
}
bool flag=false;
for(int i=1;i<=n&&!flag;i++)
{
if(!du[i])
{
int x=fa[i];
if(du[x]>1)
{
flag=true;
}
}
}
for(int i=1;i<=n&&!flag;i++)
{
if(!du[i])
{
if(depth[i]%2)
flag=true;
}
}
if(flag)
printf("Takeru\n");
else
printf("Meiya\n");
}
return 0;
}
Escape
- 分层图 & 网络流
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,rob,exc;
int h[1000010],e[1000010],ne[1000010],capacity[1000010],flow[1000010],idx;
int S,T;
char cell[110][110];
int dis[60010],curedge[1000010];
void add(int a,int b,int c)
{
e[idx]=b;
ne[idx]=h[a];
capacity[idx]=c;
flow[idx]=0;
h[a]=idx++;
}
void init()
{
memset(h,-1,sizeof h);
idx=0;
}
void input()
{
scanf("%d%d%d%d",&n,&m,&rob,&exc);
T=n*m*6;
for(int i=1;i<=n;i++)
{
scanf("%s",cell[i]+1);
}
for(int i=1;i<=rob;i++)
{
int p;
scanf("%d",&p);
add(S,p,1);
add(p,S,0);
add(p,p+m,0x3f3f3f3f);
add(p+m,p,0);
}
for(int i=1;i<=exc;i++)
{
int p;
scanf("%d",&p);
int id=n*m*2+p;
add(id,n*m*5+p,0x3f3f3f3f);
add(n*m*5+p,id,0);
add(n*m*5+p,T,0x3f3f3f3f);
add(T,n*m*5+p,0);
}
}
void build()
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(cell[i][j]=='1')
continue;
int up=i*m+j,down=up+n*m,left=up+n*m*2,right=up+n*m*3;
add(up,down,1);
add(down,up,0);
add(left,right,1);
add(right,left,0);
add(down,left,0x3f3f3f3f);
add(left,down,0);
add(right,up,0x3f3f3f3f);
add(up,right,0);
if(cell[i-1][j]!='1'&&i-1>=1)
{
add(down,(i-1)*m+j,0x3f3f3f3f);
add((i-1)*m+j,down,0);
}
if(cell[i+1][j]!='1'&&i+1<=n)
{
add(down,(i+1)*m+j,0x3f3f3f3f);
add((i+1)*m+j,down,0);
}
if(cell[i][j-1]!='1'&&j-1>=1)
{
add(right,left-1,0x3f3f3f3f);
add(left-1,right,0);
}
if(cell[i][j+1]!='1'&&j+1<=m)
{
add(right,left+1,0x3f3f3f3f);
add(left+1,right,0);
}
}
}
}
bool bfs()
{
memset(dis,0x3f,sizeof dis);
queue<int> q;
q.push(S);
dis[S]=0;
while(q.size())
{
int u=q.front();
q.pop();
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(dis[v]==0x3f3f3f3f&&capacity[i]-flow[i]>0)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[T]!=0x3f3f3f3f;
}
int dfs(int u,int limit)
{
if(u==T)
return limit;
int delta=limit;
for(int& i=curedge[u];i!=-1;i=ne[i])
{
int v=e[i];
if(dis[v]==dis[u]+1&&capacity[i]-flow[i]>0)
{
int d=dfs(v,min(delta,capacity[i]-flow[i]));
flow[i]+=d;
flow[i^1]-=d;
delta-=d;
}
if(delta==0)
break;
}
return limit-delta;
}
int dinic()
{
int ans=0;
while(bfs())
{
for(int i=0;i<=6*n*m;i++)
{
curedge[i]=h[i];
}
ans+=dfs(S,0x3f3f3f3f);
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
input();
build();
int ans=dinic();
if(ans==rob)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}