A. Cancel the Trains
题意:竖着的有好多车站,可能有火车。横着的也是一样。现在告诉你横着的哪些有火车,竖着的哪些点有火车出发。火车出发时间相同,速度相同。底下的火车只能往上走。左边的只能往右。可能有的火车会发生碰撞。问最小取消掉多少火车,就不会发生碰撞了。
思路:题目看着一大坨,其实就是判断两个数组有多少个相同元素。暴力。
AC代码:
#include
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
signed main(){
cin>>t;
while(t--){
cin>>n>>m;
for(int i = 0 ; i < n ; i ++) cin>>a[i];
for(int i = 0 ; i < m ; i ++) cin>>b[i];
int cnt = 0;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
if(a[i] == b[j]) cnt ++;
}
}
cout<<cnt<<endl;
}
return 0;
}
B. Suffix Operations
题意:一个数组。可以对数组的后缀同时进行+1或者-1操作。就是每次后 i 个元素同步操作。而且可以删掉一个数。问 最少需要操作多少次,能够变成全部相同的值。
思路:对后缀进行操作。那肯定得先让a[n]变成a[n-1],然后 a[n-1] 变成 a[n-2],操作次数就是他们的差值绝对值。 然后可以删掉一个数。其实就相当于是可以跳过一个数。然后枚举每个数删掉的情况下的答案。就可以得到最优解了。
AC代码:
#include
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
signed main(){
cin>>t;
while(t--){
cin>>n;
for(int i = 0 ; i < n ; i ++) cin>>a[i];
if(n <= 2){
cout<<0<<endl;
continue;
}
int maxx = 0;
int sum = 0;
for(int i = n-2 ; i >= 0 ; i --){
sum += abs(a[i]-a[i+1]);
}
int minn = sum-max(abs(a[n-2]-a[n-1]),abs(a[0]-a[1]));
for(int i = n-3 ; i >= 0 ; i --){
int tmp1 = abs(a[i]-a[i+1])+abs(a[i+1]-a[i+2]);
int tmp2 = abs(a[i]-a[i+2]);
minn = min(minn,sum-tmp1+tmp2);
}
C. Triangles
题意:一个n*n的数字矩阵。相同的数可以连成边。并且,可以改其中的一个数字。求一个三角形,三角形的一边平行于 x轴或者y轴,就是水平或者数值。求这个最大的三角形的面积的。
思路:枚举所有点作为三角形的一个顶点。然后要造一个三角形。可以改一个数。那么就造两次。一次造横着的边。也就是在水平方向上,这个点能延申的最大距离。然后在找出,在竖直方向上离他距离最远的数字相同的点。然后另一个就是造一条竖直方向上的边,然后找水平方向上最远的数字相同的点。然后去最大值。最后的答案就是所有的三角形的最大值。 需要预处理的就是,距离当前点的最远的点。其实只要找到这个数字,在两个方向上的最高点和最低点就行了 。然后就可以算哪个距离更大,就选哪个。
AC代码:
#include
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
char s[2005][2005];
int l[10],r[10],u[10],d[10];
signed main(){
cin>>t;
while(t--){
cin>>n;
for(int i = 0 ; i < 10 ; i ++){
l[i] = u[i] = 1e18;
r[i] = d[i] = 0;
}
for(int i = 0 ; i < n ; i ++) cin>>s[i];
for(int j = 0 ; j < n ; j ++){
for(int k = 0 ; k < n ; k ++){
int id = s[j][k] - '0';
l[id] = min(l[id],k);
r[id] = max(r[id],k);
u[id] = min(u[id],j);
d[id] = max(d[id],j);
}
}
int res[10] = {0};
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < n ; j ++){
int id = s[i][j]-'0';
int h = max(i,n-i-1);
int w = max(j,n-j-1);
int w1=0,h1=0;
w1 = max(abs(j-l[id]),abs(j-r[id]));
h1 = max(abs(i-u[id]),abs(i-d[id]));
D. Checkpoints
题意:一种游戏。很多关,有的关卡是复活点。有的不是。打每一关都有1/2的概率获胜,获胜就可以继续往下走,反之,要回到离当前最近的复活点,然后重新开始打。给出最后的期望通关次数。求关卡的分布。
思路:找数学递推公式。推出 1000这样排的情况下,期望次数是是多少,结论是:对于任意情况的n时,f =2n+1−2。然后100100这样的就可以拆成100 + 100 的期望次数。然后就可以很好找出答案了。每个期望值都对应一种光卡的排列,把他们合并起来就好了。
数学永远的痛!递推过程可以参考博客:https://www.cnblogs.com/ghostlx/p/14090287.html
AC代码:
#include
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
void test(){
exit(0);
}
int pre[100];
void prework(){
int now = 2;
for(int i = 1; now <= inf; i ++){
pre[i] = now-2;
now <<= 1;
}
}
signed main(){
prework();
int t = 1; cin>>t;
while(t--){
cin>>n;
if(n&1){ cout<<-1<<endl; continue; }
int res = 0;
for(int i = 59 ; i >= 2 && n; i--){
while(pre[i] <= n){
n -= pre[i];
E. Dog Snacks
题意:一颗树。树上每个点都有一个饼干。现在有一只狗,从根节点出发,并且要吃掉所有的饼干并且回到根节点。狗会每次找最近的饼干吃。然是距离超过k的,就吃不到了。如果不能保持都有饼干在k距离以内,任务就失败了。现在就是给出这样一棵树。然后让你求最小的k,使得任务可以完成。
思路:树形DP。对于每一个点,贪心的考虑当前点所需要的k。
假设现在在 根节点。 那么要吃掉整棵树。并且跳回来。如果有多颗子树。那肯定是先吃小的那几颗先。然后,最后吃的是深度最大的。并且深度最大的要跳回根节点。如果第二大的链。那么吃完之后,就要经过根节点,跳到最大的那跳链上去。
如果不是在根节点。那么当前点如果是叶子。就需要跳回根节点。要去吃一颗子树,肯定是希望,吃完这颗子树之后,停留的位置,深度越小越好。所以dp[pos],表示pos的子树吃完能停留的最小深度。每颗子树吃完之后,都要跳到父亲节点的下一条链去。
AC代码:
#include
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
vector<int> edge[N];
void test(){
exit(0);
}
void prework(){
}
int res = 0;
int dp[N];
void dfs(int pos,int fa,int dep){
vector<int> depp;
for(auto i : edge[pos]){
if(i!=fa){
dfs(i,pos,dep+1);
depp.push_back(dp[i]);
}
}
sort(depp.begin(),depp.end());
int sz = depp.size();
if(sz == 0){
dp[pos] = dep;
}else if(pos != 1){
dp[pos] = depp[0];
res = max(res,depp[sz-1]-dep+1);
}else if(pos == 1){
if(sz > 0) res = max(res,depp[sz-1]-dep);
if(sz > 1) res = max(res,depp[sz-2]-dep+1);
}
}
signed main(){
prework();
int t = 1; cin>>t;
while(t--){
int n;cin>>n;
for(int i = 1 ; i <= n ; i ++){
edge[i].clear();
}
for(int i = 1 ; i < n ; i ++){
int x,y;
cin>>x>>y;
edge[x].push_back(y);
edge[y].push_back(x);
}
res = 0;
dfs(1,-1,0);
cout<<res<<endl;
}
return 0;
}
F. Even Harder
题意:给定一个数组,开始在0位置,每个位置都可以移动到 i+1≤ j ≤i+ai,也就是可以跳 到下 a[i] 格子以内的各自。现在要求 把多少个点改成0可以使得从1-n只有一条唯一路径,求最小的要改变的个数。
思路:状态好难想。f[i][j+a[j] ] 表示的是,从j点出发到s,且最远能到 j+a[j],的最小修改次数。要确保唯一性,就得保证,j - i 之内的,所有可以到达所有 i 的点都被删掉了。所以这个用cnt记录下来。这个很好维护。先枚举i,然后倒着枚举 j,如果j 能到i,那么显然 cnt 就+1 。所以转移就是:
if(j+a[j] >= i){
dp[i][j+a[j]] = min(dp[i][j+a[j]],dp[j][i-1]+cnt);
cnt++;
}
AC代码:
#include
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
vector<int> edge[N];
int dp[3005][3005];
int cnt[3005][3005];
int minn[30005];
signed main(){
int t = 1; cin>>t;
while(t--){
int n;cin>>n;
for(int i = 1; i <= n ; i ++) cin>>a[i];
for(int i = 2; i <= n ; i ++) dp[1][i] = 0;
for(int i = 2; i <= n ; i ++){
int cnt = 0;
for(int j = i ; j <= n ; j ++) dp[i][j] = inf;
for(int j = i-1 ; j >= 1 ; j --){
if(j+a[j] >= i){
dp[i][j+a[j]] = min(dp[i][j+a[j]],dp[j][i-1]+cnt);
cnt++;
}
}
for(int j = i+1 ; j <= n+1 ; j ++){
dp[i][j] = min(dp[i][j],dp[i][j-1]);
}
}
cout<<dp[n][n]<<endl;
}
return 0;
}