2025-03-01 VP
这是我这学期第一次打到团队赛,也是今年第一次。另外两个小伙伴应该是以前从来没打过团队赛。打了三小时左右,但1时17分之后,就没有AC过了。还行啦,慢慢来,先一周一次团队赛保持下感觉好了。
2024四川省大学生程序设计竞赛¶
比赛链接: https://codeforces.com/gym/105222
榜单链接: https://board.xcpcio.com/provincial-contest%2F2024%2Fsichuan?group=official
赛时只通过了三题: E H L
H 可能稍微要推一会,E 单纯是题目描述非常迷惑。如果没有理解问题,感觉也是十多分钟能AC的。L 就排个序就行。。。这次真就只签了个到。后面的写不了了。
B¶
疯狂分类讨论。。。
要非常仔细才行,千万不能写得烦。VP本来就应该写出来的,早知道稍微多看一会,好好仔细讨论一下,想一下就好了。
现在再看全部重新写,一小时WA了11次然后过了。
void solve()
{
int ans = 0;
array<int, 6> a;
for (int i = 1; i <= 5; i++)
cin >> a[i];
// 0 0 3 0 3
ans += a[3] / 2, a[3] %= 2;
// 如果4需要3,那么2肯定没了。
{
int mn = min(a[2], a[4]);
ans += mn, a[2] -= mn, a[4] -= mn;
}
// 3 给自己用肯定是最优的,剩下的先不要急着变成1吧。说不定能 1 2 3加一起
{
int mn = min(a[1], a[5]);
ans += mn, a[1] -= mn, a[5] -= mn;
mn = min(a[3], a[5]);
ans += mn, a[3] -= mn, a[5] -= mn;
mn = min(a[2], a[5]);
ans += mn, a[2] -= mn, a[5] -= mn;
mn = min(a[4], a[5]);
ans += mn, a[4] -= mn, a[5] -= mn;
// 5 肯定一个都没有了 或者其他的一个都没有了
// 好吧 ,5可能还有
ans += a[5] / 2, a[1] += a[5] % 2, a[5] = 0;
}
// 3 2 1 一起 ,还是给4?
// 1 2 3组一起的话一定是需要1的,最好先别把1用完
{
// 如果还有4,那么肯定没有2了,只剩下1
int mn = min(a[1] / 2, a[4]);
ans += mn, a[1] -= 2 * mn, a[4] -= mn;
// 如果a[4]还有,那么肯定最多1个1
ans += a[4] / 3, a[4] %= 3;
if (a[4] == 2) {
if (a[1]) {
a[1]--, a[4] = 0, ans++;
} else if (a[3]) {
a[3]--, a[4] = 0, ans++;
} else {
a[1] += a[4], a[4] = 0;
}
} else if (a[4] == 1) {
assert(a[1] <= 1);
if (a[3] && a[1]) {
a[3]--, a[1]--, a[4]--, ans++;
} else {
a[1] += a[4], a[4] = 0;
}
}
}
if (a[3]) {
// 2 现在只能给 3 用了
if (a[2] && a[1]) {
ans++, a[2]--, a[1]--, a[3] = 0;
} else if (a[1] >= 3) {
a[1] -= 3, ans++, a[3] = 0;
} else if (a[2] >= 2) {
a[2] -= 2, a[3] = 0, ans++;
} else {
a[1] += a[3], a[3] = 0;
}
// 如果这里不能消耗3,那么转换成1
}
{
// 可能还有2
ans += a[2] / 3, a[2] %= 3;
}
{
// 可能还会剩1和2
if (a[2] == 1) {
if (a[1] >= 4) {
ans++, a[2] = 0, a[1] -= 4;
}
} else if (a[2] == 2) {
if (a[1] >= 2) {
ans++, a[2] = 0, a[1] -= 2;
}
}
// 只剩1了应该
ans += a[1] / 6, a[1] %= 6;
}
// for (int i = 1; i <= 5; i++)
// cerr << a[i] << " \n"[i == 5];
cout << ans << '\n';
}
E¶
要用 L 形来填满网格。但是并非填满。它题目,我们明明看着是如果还剩一个格子没有被覆盖,且没有覆盖的格子在第一行第 \(m\) 列,那么这个也是一个合法的方案。但是实际上,说的是必须空一个格子不被覆盖,且必须在第 \(1\) 行第 \(m\) 列空着。
那么就很好判断了。直接就检查是不是三个一组,是不是每个格子都被覆盖。
幸好有样例第二个,不然真看不出来。
constexpr int N = 500*500;
int cnt[N+1];
void solve()
{
int n,m;
cin>>n>>m;
vector<string>s(n);
for(int i=0;i<n;i++){
cin>>s[i];
}
vector<vector<int>>C(n+1,vector<int>(m+1));
int tot=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
if(s[i][j]=='C'){
C[i][j]=++tot;
cnt[tot]=0;
if(i-1>=0&&s[i-1][j]=='D'){
C[i-1][j]=tot;
}
if(i+1<n&&s[i+1][j]=='U'){
C[i+1][j]=tot;
}
if(j-1>=0&&s[i][j-1]=='R'){
C[i][j-1]=tot;
}
if(j+1<m&&s[i][j+1]=='L'){
C[i][j+1]=tot;
}
}
}
// cout<<'\n';
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
// cout<<C[i][j]<<" \n"[j+1==m];
cnt[C[i][j]]++;
}
int all=0;
for(int i=1;i<=tot;i++){
// cerr<<cnt[i]<< '\n';
all+=cnt[i];
if(cnt[i]!=3){
cout<<"No\n";
return;
}
}
// cerr<<all<<'\n';
// if(all==n*m){
// cout<<"Yes\n";
// }else
if(all==n*m-1&&s[0][m-1]=='.')
cout<<"Yes\n";
else
cout<<"No\n";
}
F¶
还真不难,也是稍微想一下就行。
下次比赛真的得好好思考,这场其实可写题还挺多的来着。BF如果多想会是能出的。补这2题都没看题解,感觉确实不难的。
这题我只搜了一下怎么判断直线过矩形。。。其实向量怎么表示直线方程也不太记得。
假如方向向量是 \((vx,vy)\) 那么直线方程是 \(vx(y-y_0)=vy(x-x_0)\) , \((x_0,y_0)\) 为起点位置。判断两点是否在直线同侧,只需要看带入直线方程后结果的正负。同号则同侧,等于0则在直线上。
我们可以算出圆心在矩形中的合法范围,也就是 \((lx+r,ly+r)\) 和 \((rx-r,ry-r)\) 之间的矩形区域。我们只需要看圆心是否会经过这个区域就行了。
可以判断是否和矩形的每条边相交。也可以直接将四个点带入直线方程,判断是否四个点都位于直线同侧。但是要注意的这个直线是有方向的,也许会往反方向走,但是判的能相交,这个可以特判一下直接输出。
void solve()
{
int x, y, r, vx, vy;
cin >> x >> y >> r >> vx >> vy;
// 可以直接算出圆心放在哪个矩形可以使得满足条件。
// 然后判断圆心的运动轨迹是否会经过这个区域。
// 可以将顶点都带入直线方程,看是否都在直线同侧。
// 运动路径可以表示为 vx * (Y-y) = vy * (X-x)
// 圆心需要在
int lx, ly, rx, ry;
cin >> lx >> ly >> rx >> ry;
// 圆心必须在 (lx+r,ly+r) 和 (rx-r,ry-r) 之间
if (rx - lx < 2 * r || ry - ly < 2 * r) {
cout << "No\n";
return;
}
// 由于是有方向的 我们直接来简单判断一下会不会经过
if (x > rx - r || y > ry - r) {
if (vx >= 0 && vy >= 0) {
cout << "No\n";
return;
}
}
if (y > ry - r || x < lx + r) {
if (vx <= 0 && vy >= 0) {
cout << "No\n";
return;
}
}
if (y < ly + r || x < lx + r) {
if (vx <= 0 && vy <= 0) {
cout << "No\n";
return;
}
}
if (y < ly + r || x > rx - r) {
if (vx >= 0 && vy <= 0) {
cout << "No\n";
return;
}
}
auto calc = [&](int X, int Y) {
return 1LL * vx * (Y - y) - 1LL * vy * (X - x);
};
set<long long> st;
st.insert(calc(lx + r, ly + r));
st.insert(calc(lx + r, ry - r));
st.insert(calc(rx - r, ry - r));
st.insert(calc(rx - r, ly + r));
if (((*st.begin() < 0) && (*st.rbegin() > 0)) || st.contains(0))
cout << "Yes\n";
else
cout << "No\n";
}
H¶
稍微推一下前面几个啊,然后就可以 guess 了,找规律。
//1 G ,1
//2 G ,2
//3 Y ,1
//4 G ,2
//5 G ,3 , 3 Y , 1 G
//6 Y ,2
//7 G ,3
//8 G ,4
//9 Y ,3 , 7 Y , 3
//10 G
//11 G
这是我们推的,就是 \(n\) 为多少,然后谁能赢,能拿到多少个石头。
写这么几个大概就能看出来,一定是可以直接确定谁能赢的。两个人都希望能拿走尽可能多的石头,所以如果你怎么选都是输,那肯定就是拿 2 个。
前面推的就是,显然 1 和 2 个石头时,先手必赢。如果是 3 个石头,那么先手的人一定会把他转换成 2 或 1,且另一个人先手,于是变成另一个人的必胜状态。所以就多推几个就可以猜啦。
void solve()
{
long long n;
cin>>n;
long long ans=(n-1)/3+1;
if(n%3==0){
cout<<"1 ";
cout<<ans<<'\n';
}else{
cout<<"0 ";
if(n%3==2) cout<<ans+1<<'\n';
else cout<<ans<<'\n';
}
}
L¶
这个就是很签到啦。
就看把哪个放到第一个锅,哪个放第二个锅。
然后按值排序输出下标即可。
void solve()
{
int n;
cin >> n;
vector<pair<int, int>> v1, v2;
for (int i = 1; i <= n; i++) {
int a, b, c, d;
cin >> a >> b >> c >> d;
if (c == d) {
if (a < b) {
v1.push_back({ a, i });
} else {
v2.push_back({ b, i });
}
} else if (c)
v1.push_back({ a, i });
else
v2.push_back({ b, i });
}
sort(v1.begin(),v1.end(),[](pair<int,int>x,pair<int,int>y){
return x.first<y.first;
});
sort(v2.begin(),v2.end(),[](pair<int,int>x,pair<int,int>y){
return x.first<y.first;
});
cout<<v1.size()<<' ';
for(auto [_,i]:v1)
cout<<i<<" ";
cout<<'\n';
cout<<v2.size()<<' ';
for(auto [_,i]:v2)
cout<<i<<" ";
}