D The Game of Eating 结论,贪心
E Square 数论,枚举
F Link with Chess Game 博弈论,打表
G Link with Centrally Symmetric Strings 最长回文子串,结论Manacher算法
H 0 and 1 in BIT 线段树,结论推导I Link with Gomoku 构造
K Box 动态规划
观察样例知,从头开始每次选择最大的时候,不会更优。反而倒着每次选择最大值,会得出解。而至于正确性,很难证明。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[2020][2020];
int book[2020];
struct node
{
int id,val;
friend bool operator<(node x,node y)
{
return x.val<y.val;
}
};
priority_queue<node>q[2020];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
book[i]=0;
}
for(int i=1;i<=n;i++)
{
while(!q[i].empty())
q[i].pop();
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
struct node now;
now.id=j;
now.val=a[i][j];
q[i].push(now);
}
}
for(int i=k;i>=1;i--)
{
int now=i%n;
if(now==0)
now=n;
while(!q[now].empty()&&book[q[now].top().id])
q[now].pop();
book[q[now].top().id]=1;
q[now].pop();
}
for(int i=1;i<=m;i++)
{
if(book[i])
{
cout<<i<<" ";
}
}
cout<<'\n';
}
return 0;
}
考虑将x开平方,再平方,如果恰好能够得出最好,如果不能得出,必须对要平方的数字加1,且只加1更接近这一前缀。但这一加1操作,会导致同等长度的前缀大于x,而考虑到+1操作带来的额外影响会随着x*10不断变小,故对x不停的*10,算出x为这些数字前缀时,y是否符合。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
bool check(ll y,ll x)
{
ll now=y*y;
ll ji=1;
while(ji<=now)
{
if(now/ji==x)
return 1;
if(ji>=1e18)
break;
ji*=10ll;
}
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
ll x;
cin>>x;
if(x==0)
{
cout<<0<<'\n';
continue;
}
else
{
ll ans=-1;
for(ll now=x;now<=1e18;now*=10ll)
{
ll temp=sqrt(now);
if(temp*temp<now)
temp++;
if(check(temp,x))
{
ans=temp;
break;
}
}
cout<<ans<<'\n';
}
}
return 0;
}
打表找规律,首先对于一个,r,g,b状态,如果他的后续状态有输的,就代表我先手当前一定可以通过到达这一后续状态来实现赢。dfs暴力打表,可以得出,n为偶数时,一定是先手赢。否则,是先后手交替赢。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t;
cin>>t;
while(t--)
{
ll n,a,b,c;
cin>>n>>a>>b>>c;
if(n%2==0)
{
cout<<"Alice"<<'\n';
continue;
}
ll sum=(a-1)*n*n+(b-1)*n+c;
if(sum%2)
{
cout<<"Bob"<<'\n';
}
else
{
cout<<"Alice"<<'\n';
}
}
return 0;
}
首先一个重要的结论是,回文串是能割就割。例如,
1 2 3 4 4 3 2 1
1 2 3 4 4 3 2 1 .... 1 2 3 4 4 3 2 1
在同时面对割小串还是大串时,先割大串与割三个小串等价。故只需要找到能割的就割。
考虑manacher算法。起点开始时,如果发现一个回文串,也就是回文串中点到达了1号点,就割掉。然后令设置下一个起点即可。而为了防止新的起点之后的遍历跑到起点之前寻找回文串前部分,直接让新起点之前的字符变成无法匹配的即可。
最后只需要判断,最终的起点,是不是n即可。如果是n,代表全部切割,否则无法实现。
# include<bits/stdc++.h>
#define MN 1000000
using namespace std;
int rev[256];
char _s[MN+5],s[MN*2+5];
bool dc(char a,char b){
return b==rev[a];
}
bool checkzhong(char ch)
{
return (ch=='o'||ch=='s'||ch=='x'||ch=='z'||ch=='#');
}
void init(){
for(int i=0;i<256;i++){
rev[i] = 1;
}
rev['b'] = 'q';
rev['d'] = 'p';
rev['p'] = 'd';
rev['q'] = 'b';
rev['n'] = 'u';
rev['u'] = 'n';
rev['o'] = 'o';
rev['s'] = 's';
rev['x'] = 'x';
rev['z'] = 'z';
rev['#'] = '#';
}
void solve(){
scanf("%s",_s);
int n = strlen(_s);
s[0] = '$';
for(int i=0;i<n;i++){
s[i*2+1] = '#';
s[i*2+2] = _s[i];
}
n = n*2;
s[++n] = '#';
s[n+1] = '@';
n++;
int rc = 0;
vector<int> r(n+1);
int start = 1;
for(int i=1;i<=n;i++){
if(rc+r[rc]>=i){
r[i] = min(r[rc-(i-rc)],r[rc]-(i-rc));
}else{
if(checkzhong(s[i]))
r[i]=1;
else
r[i]=0;
}
while(dc(s[i-r[i]],s[i+r[i]])){
r[i]++;
}
if(i+r[i]>rc+r[rc]){
rc = i;
}
if(r[i]>0&&i-r[i]<start){
s[i+r[i]-1] = '$';
start = i+r[i];
i = i+r[i]-1;
rc=0;
}
}
puts(start==n?"Yes":"No");
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--) solve();
}
以0100100为例
反加 0100100->1011011-> 1011100 等于 0100011 1011100 先减后取反
加反 0100100->0100101-> 1011010 等于 先加后取反
左边取反,右边要减的变成加 保持不变
反加 反加
1011100 0100011 0100100 保持不变
左边取反,左边本来要减,右边原来加的变成减
反加 加反
1011100 1011101 0100010 原数-2
左边加1,取反后,右边加的要减 保持不变
加反 加反
1011010 1011011 0100100 保持不变
左边加1,取反后,右边减的变成加, 原数加2
加反 反加
1011010 0100101 0100110 原数加2所以,可以线段树维护,存储当前区间是否有翻转和所加值大小(可以负数)。
最终先加值,再看是否翻转。
而,因为减的值可能过大,大于原数,考虑取模。
即,0001进行一次翻转后加三次,
0001->1110->1111->0000->0001
也就是0001->0000->(-1)->(15)->1111->(14)->1110->翻转-0001
所以对(1<<len)取模即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll change[200000*4+10],add[200000*4+10];
string s;
void pushup(int root)
{
change[root]=change[root<<1]^change[root<<1|1];
if(change[root<<1])
add[root]=add[root<<1]-add[root<<1|1];
else
add[root]=add[root<<1]+add[root<<1|1];
}
void build(int root,int l,int r)
{
if(l==r)
{
change[root]=(s[l]=='A');
add[root]=(s[l]=='B');
return ;
}
int mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
pushup(root);
}
pair<ll,ll>getans(int root,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
{
return make_pair(change[root],add[root]);
}
int mid=(l+r)>>1;
pair<ll,ll>ans;
if(L<=mid)
ans=getans(root<<1,l,mid,L,R);
if(R>mid)
{
pair<ll,ll>temp=getans(root<<1|1,mid+1,r,L,R);
if(ans.first)
ans.second=ans.second-temp.second;
else
ans.second=ans.second+temp.second;
ans.first=ans.first^temp.first;
}
return ans;
}
int main()
{
/*
0100100
反加 0100100->1011011-> 1011100 等于 0100011 1011100 先减后取反
加反 0100100->0100101-> 1011010 等于 先加后取反
左边取反,右边要减的变成加 保持不变
反加 反加
1011100 0100011 0100100 保持不变
左边取反,左边本来要减,右边原来加的变成减
反加 加反
1011100 1011101 0100010 原数-2
左边加1,取反后,右边加的要减 保持不变
加反 加反
1011010 1011011 0100100 保持不变
左边加1,取反后,右边减的变成加, 原数加2
加反 反加
1011010 0100101 0100110 原数加2
10 3
BAABABABBA
2 4
110
*/
int n,t;
cin>>n>>t;
cin>>s;
s=" "+s;
build(1,1,n);
ll l,r,ans=0;
while(t--)
{
scanf("%lld%lld",&l,&r);
ll prel=l,prer=r;
l=min((ans^prel)%n+1,(ans^prer)%n+1);
r=max((ans^prel)%n+1,(ans^prer)%n+1);
int flag=getans(1,1,n,l,r).first;
ll add=getans(1,1,n,l,r).second;
string now;
cin>>now;
ll val=0;
for(int i=now.length()-1; i>=0; i--)
{
if(now[i]=='1')
val+=(1ll<<(now.length()-1-i));
}
val+=add;
ll mod=(1ll<<(now.length()));
ll vall=0;
val%=mod;
val+=mod;
val%=mod;
for(int i=now.length()-1; i>=0; i--)
{
if((val&(1ll<<i)))
{
vall+=(1ll<<i);
}
}
ll ans1=vall;
if(flag)
{
ll temp=0;
for(int i=0; i<now.length(); i++)
{
if((vall&(1ll<<i))==0)
{
temp+=(1ll<<i);
}
}
ans1=temp;
}
ans=0;
for(int i=now.length()-1; i>=0; i--)
{
if((ans1&(1ll<<i)))
{
cout<<1;
ans+=(1ll<<i);
}
else
cout<<0;
}
cout<<'\n';
}
return 0;
}
每行四个四个构造就行
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7;
int main()
{
int t;
cin >> t;
while (t--)
{
int n, m;
scanf("%d%d", &n, &m);
if (n % 2 == 1)
{
int now = 0;
for (int i = 1; i < n; i++)
{
int nowcnt = 0;
int nownow = now;
for (int j = 1; j <= m; j++)
{
nowcnt++;
if (nowcnt <= 4)
{
if (nownow == 0)
{
cout << "x";
}
else
{
cout << "o";
}
}
if (nowcnt == 4)
{
nowcnt = 0;
nownow ^= 1;
}
}
cout << '\n';
now ^= 1;
}
for (int i = 1; i <= m; i++)
{
if (i % 2 == 1)
{
cout << 'x';
}
else
{
cout << "o";
}
}
cout << '\n';
}
else
{
int now = 0;
for (int i = 1; i <= n; i++)
{
int nowcnt = 0;
int nownow = now;
for (int j = 1; j <= m; j++)
{
nowcnt++;
if (nowcnt <= 4)
{
if (nownow == 0)
{
cout << "x";
}
else
{
cout << "o";
}
}
if (nowcnt == 4)
{
nowcnt = 0;
nownow ^= 1;
}
}
now ^= 1;
cout << '\n';
}
}
}
return 0;
}
dp[i][0/1/2]代表前i个在第i个是保持不变还是左移右移时的dp值
当前不移动时,因为不能重叠,所以考虑dp[i-1][0],dp[i-1][1]转移
当前向左时,dp[i-1][1]转移
当前向右时,dp[i-1][0/1/2]转移
以上是位置上有盖子的情况,没有盖子时,考虑继承
因为有盖子向左转移时只能由向左转移而来,故没有盖子的向左转移只能继承之前的“不动”“向左”
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7;
bool e[N];
ll a[N],dp[N][4];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++)
{
int inn;
scanf("%d", &inn);
e[i] = inn;
}
ll res = 0;
for (int i = 1; i <= n; i++)
{
if (e[i])
{
dp[i][0] = dp[i - 1][0] + a[i - 1];
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0]) + a[i];
dp[i][2] = max(dp[i - 1][0], max(dp[i - 1][2], dp[i - 1][1])) + a[i + 1];
}
else
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
dp[i][2] = dp[i][1] = max(dp[i - 1][2], max(dp[i - 1][0], dp[i - 1][1]));
}
res = max(res, max(dp[i][0], max(dp[i][1], dp[i][2])));
}
printf("%lld\n", res);
}